refactor(ivy): unify native node handling around render parent and element / ICU containers (#27925)
PR Close #27925
This commit is contained in:
parent
8a3cebde8b
commit
e08feb7e54
|
@ -19,31 +19,16 @@ import {findComponentView, getNativeByTNode, isLContainer, isRootView, readEleme
|
||||||
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5;
|
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5;
|
||||||
|
|
||||||
/** Retrieves the native node (element or a comment) for the parent of a given node. */
|
/** Retrieves the native node (element or a comment) for the parent of a given node. */
|
||||||
export function getParentNative(tNode: TNode, currentView: LView): RElement|RComment|null {
|
function getParentNative(tNode: TNode, currentView: LView): RElement|RComment|null {
|
||||||
if (tNode.parent == null) {
|
return tNode.parent == null ? getHostNative(currentView) :
|
||||||
return getHostNative(currentView);
|
getNativeByTNode(tNode.parent, currentView);
|
||||||
} else {
|
|
||||||
const parentTNode = getFirstNonICUParent(tNode);
|
|
||||||
return getNativeByTNode(parentTNode, currentView);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the first parent of a node that isn't an IcuContainer TNode
|
|
||||||
*/
|
|
||||||
function getFirstNonICUParent(tNode: TNode): TNode {
|
|
||||||
let parent = tNode.parent;
|
|
||||||
while (parent && parent.type === TNodeType.IcuContainer) {
|
|
||||||
parent = parent.parent;
|
|
||||||
}
|
|
||||||
return parent !;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the host element given a view. Will return null if the current view is an embedded view,
|
* Gets the host element given a view. Will return null if the current view is an embedded view,
|
||||||
* which does not have a host element.
|
* which does not have a host element.
|
||||||
*/
|
*/
|
||||||
export function getHostNative(currentView: LView): RElement|null {
|
function getHostNative(currentView: LView): RElement|null {
|
||||||
const hostTNode = currentView[HOST_NODE] as TElementNode;
|
const hostTNode = currentView[HOST_NODE] as TElementNode;
|
||||||
return hostTNode && hostTNode.type !== TNodeType.View ?
|
return hostTNode && hostTNode.type !== TNodeType.View ?
|
||||||
(getNativeByTNode(hostTNode, currentView[PARENT] !) as RElement) :
|
(getNativeByTNode(hostTNode, currentView[PARENT] !) as RElement) :
|
||||||
|
@ -518,10 +503,9 @@ export function getRenderParent(tNode: TNode, currentView: LView): RElement|null
|
||||||
return nativeParentNode(currentView[RENDERER], getNativeByTNode(tNode, currentView));
|
return nativeParentNode(currentView[RENDERER], getNativeByTNode(tNode, currentView));
|
||||||
}
|
}
|
||||||
|
|
||||||
const tNodeParent = getFirstNonICUParent(tNode);
|
// skip over element and ICU containers as those are represented by a comment node and
|
||||||
if (tNodeParent != null && tNodeParent.type === TNodeType.ElementContainer) {
|
// can't be used as a render parent
|
||||||
tNode = getHighestElementContainer(tNodeParent);
|
tNode = getHighestElementOrICUContainer(tNode);
|
||||||
}
|
|
||||||
|
|
||||||
const hostTNode = currentView[HOST_NODE];
|
const hostTNode = currentView[HOST_NODE];
|
||||||
return tNode.parent == null && hostTNode !.type === TNodeType.View ?
|
return tNode.parent == null && hostTNode !.type === TNodeType.View ?
|
||||||
|
@ -593,17 +577,11 @@ function canInsertNativeChildOfView(viewTNode: TViewNode, view: LView): boolean
|
||||||
*/
|
*/
|
||||||
export function canInsertNativeNode(tNode: TNode, currentView: LView): boolean {
|
export function canInsertNativeNode(tNode: TNode, currentView: LView): boolean {
|
||||||
let currentNode = tNode;
|
let currentNode = tNode;
|
||||||
let parent: TNode|null = tNode.parent;
|
let parent: TNode|null;
|
||||||
|
|
||||||
|
currentNode = getHighestElementOrICUContainer(currentNode);
|
||||||
|
parent = currentNode.parent;
|
||||||
|
|
||||||
if (tNode.parent) {
|
|
||||||
if (tNode.parent.type === TNodeType.ElementContainer) {
|
|
||||||
currentNode = getHighestElementContainer(tNode);
|
|
||||||
parent = currentNode.parent;
|
|
||||||
} else if (tNode.parent.type === TNodeType.IcuContainer) {
|
|
||||||
currentNode = getFirstNonICUParent(currentNode);
|
|
||||||
parent = currentNode.parent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (parent === null) parent = currentView[HOST_NODE];
|
if (parent === null) parent = currentView[HOST_NODE];
|
||||||
|
|
||||||
if (parent && parent.type === TNodeType.View) {
|
if (parent && parent.type === TNodeType.View) {
|
||||||
|
@ -633,7 +611,7 @@ export function nativeInsertBefore(
|
||||||
*/
|
*/
|
||||||
export function nativeRemoveChild(renderer: Renderer3, parent: RElement, child: RNode): void {
|
export function nativeRemoveChild(renderer: Renderer3, parent: RElement, child: RNode): void {
|
||||||
isProceduralRenderer(renderer) ? renderer.removeChild(parent as RElement, child) :
|
isProceduralRenderer(renderer) ? renderer.removeChild(parent as RElement, child) :
|
||||||
parent !.removeChild(child);
|
parent.removeChild(child);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -664,27 +642,23 @@ export function appendChild(
|
||||||
childEl: RNode | null = null, childTNode: TNode, currentView: LView): boolean {
|
childEl: RNode | null = null, childTNode: TNode, currentView: LView): boolean {
|
||||||
if (childEl !== null && canInsertNativeNode(childTNode, currentView)) {
|
if (childEl !== null && canInsertNativeNode(childTNode, currentView)) {
|
||||||
const renderer = currentView[RENDERER];
|
const renderer = currentView[RENDERER];
|
||||||
const parentEl = getParentNative(childTNode, currentView);
|
const renderParent = getRenderParent(childTNode, currentView) !;
|
||||||
const parentTNode: TNode = childTNode.parent || currentView[HOST_NODE] !;
|
const parentTNode: TNode = childTNode.parent || currentView[HOST_NODE] !;
|
||||||
|
|
||||||
if (parentTNode.type === TNodeType.View) {
|
if (parentTNode.type === TNodeType.View) {
|
||||||
const lContainer = getLContainer(parentTNode as TViewNode, currentView) as LContainer;
|
const lContainer = getLContainer(parentTNode as TViewNode, currentView) !;
|
||||||
const views = lContainer[VIEWS];
|
const views = lContainer[VIEWS];
|
||||||
const index = views.indexOf(currentView);
|
const index = views.indexOf(currentView);
|
||||||
nativeInsertBefore(
|
nativeInsertBefore(
|
||||||
renderer, lContainer[RENDER_PARENT] !, childEl,
|
renderer, renderParent, childEl, getBeforeNodeForView(index, views, lContainer[NATIVE]));
|
||||||
getBeforeNodeForView(index, views, lContainer[NATIVE]));
|
} else if (
|
||||||
} else if (parentTNode.type === TNodeType.ElementContainer) {
|
parentTNode.type === TNodeType.ElementContainer ||
|
||||||
const renderParent = getRenderParent(childTNode, currentView) !;
|
parentTNode.type === TNodeType.IcuContainer) {
|
||||||
const ngContainerAnchorNode = getNativeByTNode(parentTNode, currentView);
|
const anchorNode = getNativeByTNode(parentTNode, currentView);
|
||||||
nativeInsertBefore(renderer, renderParent, childEl, ngContainerAnchorNode);
|
nativeInsertBefore(renderer, renderParent, childEl, anchorNode);
|
||||||
} else if (parentTNode.type === TNodeType.IcuContainer) {
|
|
||||||
const renderParent = getRenderParent(childTNode, currentView) !;
|
|
||||||
const icuAnchorNode = getNativeByTNode(parentTNode, currentView);
|
|
||||||
nativeInsertBefore(renderer, renderParent, childEl, icuAnchorNode);
|
|
||||||
} else {
|
} else {
|
||||||
isProceduralRenderer(renderer) ? renderer.appendChild(parentEl !as RElement, childEl) :
|
isProceduralRenderer(renderer) ? renderer.appendChild(renderParent, childEl) :
|
||||||
parentEl !.appendChild(childEl);
|
renderParent.appendChild(childEl);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -692,16 +666,17 @@ export function appendChild(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the top-level ng-container if ng-containers are nested.
|
* Gets the top-level element or an ICU container if those containers are nested.
|
||||||
*
|
*
|
||||||
* @param ngContainer The TNode of the starting ng-container
|
* @param tNode The starting TNode for which we should skip element and ICU containers
|
||||||
* @returns tNode The TNode of the highest level ng-container
|
* @returns The TNode of the highest level ICU container or element container
|
||||||
*/
|
*/
|
||||||
function getHighestElementContainer(ngContainer: TNode): TNode {
|
function getHighestElementOrICUContainer(tNode: TNode): TNode {
|
||||||
while (ngContainer.parent != null && ngContainer.parent.type === TNodeType.ElementContainer) {
|
while (tNode.parent != null && (tNode.parent.type === TNodeType.ElementContainer ||
|
||||||
ngContainer = ngContainer.parent;
|
tNode.parent.type === TNodeType.IcuContainer)) {
|
||||||
|
tNode = tNode.parent;
|
||||||
}
|
}
|
||||||
return ngContainer;
|
return tNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getBeforeNodeForView(index: number, views: LView[], containerNative: RComment) {
|
export function getBeforeNodeForView(index: number, views: LView[], containerNative: RComment) {
|
||||||
|
@ -725,7 +700,7 @@ export function getBeforeNodeForView(index: number, views: LView[], containerNat
|
||||||
export function removeChild(childTNode: TNode, childEl: RNode | null, currentView: LView): boolean {
|
export function removeChild(childTNode: TNode, childEl: RNode | null, currentView: LView): boolean {
|
||||||
// We only remove the element if not in View or not projected.
|
// We only remove the element if not in View or not projected.
|
||||||
if (childEl !== null && canInsertNativeNode(childTNode, currentView)) {
|
if (childEl !== null && canInsertNativeNode(childTNode, currentView)) {
|
||||||
const parentNative = getParentNative(childTNode, currentView) !as RElement;
|
const parentNative = getRenderParent(childTNode, currentView) !;
|
||||||
nativeRemoveChild(currentView[RENDERER], parentNative, childEl);
|
nativeRemoveChild(currentView[RENDERER], parentNative, childEl);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -258,10 +258,7 @@
|
||||||
"name": "getDirectiveDef"
|
"name": "getDirectiveDef"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "getFirstNonICUParent"
|
"name": "getHighestElementOrICUContainer"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "getHighestElementContainer"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "getHostNative"
|
"name": "getHostNative"
|
||||||
|
|
|
@ -674,14 +674,11 @@
|
||||||
{
|
{
|
||||||
"name": "getElementDepthCount"
|
"name": "getElementDepthCount"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "getFirstNonICUParent"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "getFirstTemplatePass"
|
"name": "getFirstTemplatePass"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "getHighestElementContainer"
|
"name": "getHighestElementOrICUContainer"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "getHostNative"
|
"name": "getHostNative"
|
||||||
|
|
|
@ -713,7 +713,7 @@ describe('Runtime i18n', () => {
|
||||||
expect(fixture.html)
|
expect(fixture.html)
|
||||||
.toEqual(
|
.toEqual(
|
||||||
'<div><span>3 <span title="emails label">emails</span><!--ICU 4--></span></div>');
|
'<div><span>3 <span title="emails label">emails</span><!--ICU 4--></span></div>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('for ICU expressions inside <ng-container>', () => {
|
it('for ICU expressions inside <ng-container>', () => {
|
||||||
const MSG_DIV = `{<7B>0<EFBFBD>, plural,
|
const MSG_DIV = `{<7B>0<EFBFBD>, plural,
|
||||||
|
@ -721,20 +721,24 @@ describe('Runtime i18n', () => {
|
||||||
=1 {one <i>email</i>}
|
=1 {one <i>email</i>}
|
||||||
other {<EFBFBD>0<EFBFBD> <span title="<22>1<EFBFBD>">emails</span>}
|
other {<EFBFBD>0<EFBFBD> <span title="<22>1<EFBFBD>">emails</span>}
|
||||||
}`;
|
}`;
|
||||||
const fixture = prepareFixture(() => {
|
const fixture = prepareFixture(
|
||||||
elementStart(0, 'div'); {
|
() => {
|
||||||
elementContainerStart(1); {
|
elementStart(0, 'div');
|
||||||
i18n(2, MSG_DIV);
|
{
|
||||||
}
|
elementContainerStart(1);
|
||||||
elementContainerEnd();
|
{ i18n(2, MSG_DIV); }
|
||||||
}
|
elementContainerEnd();
|
||||||
elementEnd();
|
}
|
||||||
}, () => {
|
elementEnd();
|
||||||
i18nExp(bind(0));
|
},
|
||||||
i18nApply(2);
|
() => {
|
||||||
}, 3, 1);
|
i18nExp(bind(0));
|
||||||
|
i18nExp(bind('more than one'));
|
||||||
|
i18nApply(2);
|
||||||
|
},
|
||||||
|
3, 2);
|
||||||
|
|
||||||
expect(fixture.html).toEqual('<div>no <b title="none">emails</b>!<!--ICU 4--></div>');
|
expect(fixture.html).toEqual('<div>no <b title="none">emails</b>!<!--ICU 5--></div>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('for nested ICU expressions', () => {
|
it('for nested ICU expressions', () => {
|
||||||
|
|
Loading…
Reference in New Issue