fix(ivy): ensure parent/sub-class components evaluate styling correctly (#29602)
The new styling algorithm in angular is designed to evaluate host bindings stylinh priority in order of directive evaluation order. This, however, does not work with respect to parent/sub-class directives because sub-class host bindings are run after the parent host bindings but still have priority. This patch ensures that the host styling bindings for parent and sub-class components/directives are executed with respect to the styling algorithm prioritization. Jira Issue: FW-1132 PR Close #29602
This commit is contained in:

committed by
Igor Minar

parent
5c13feebfd
commit
ec56354306
@ -10,14 +10,14 @@ import {EMPTY_ARRAY, EMPTY_OBJ} from '../empty';
|
||||
import {AttributeMarker, TAttributes} from '../interfaces/node';
|
||||
import {BindingStore, BindingType, Player, PlayerBuilder, PlayerFactory, PlayerIndex} from '../interfaces/player';
|
||||
import {RElement, Renderer3, RendererStyleFlags3, isProceduralRenderer} from '../interfaces/renderer';
|
||||
import {DirectiveOwnerAndPlayerBuilderIndex, DirectiveRegistryValues, DirectiveRegistryValuesIndex, InitialStylingValues, InitialStylingValuesIndex, MapBasedOffsetValues, MapBasedOffsetValuesIndex, SinglePropOffsetValues, SinglePropOffsetValuesIndex, StylingContext, StylingFlags, StylingIndex} from '../interfaces/styling';
|
||||
import {DirectiveOwnerAndPlayerBuilderIndex, DirectiveRegistryValuesIndex, InitialStylingValues, InitialStylingValuesIndex, MapBasedOffsetValues, MapBasedOffsetValuesIndex, SinglePropOffsetValues, SinglePropOffsetValuesIndex, StylingContext, StylingFlags, StylingIndex} from '../interfaces/styling';
|
||||
import {LView, RootContext} from '../interfaces/view';
|
||||
import {NO_CHANGE} from '../tokens';
|
||||
import {getRootContext} from '../util/view_traversal_utils';
|
||||
|
||||
import {allowFlush as allowHostInstructionsQueueFlush, flushQueue as flushHostInstructionsQueue} from './host_instructions_queue';
|
||||
import {BoundPlayerFactory} from './player_factory';
|
||||
import {addPlayerInternal, allocPlayerContext, allocateDirectiveIntoContext, createEmptyStylingContext, getPlayerContext} from './util';
|
||||
|
||||
import {addPlayerInternal, allocPlayerContext, allocateOrUpdateDirectiveIntoContext, createEmptyStylingContext, getPlayerContext} from './util';
|
||||
|
||||
|
||||
/**
|
||||
@ -42,9 +42,9 @@ import {addPlayerInternal, allocPlayerContext, allocateDirectiveIntoContext, cre
|
||||
* Creates a new StylingContext an fills it with the provided static styling attribute values.
|
||||
*/
|
||||
export function initializeStaticContext(
|
||||
attrs: TAttributes, stylingStartIndex: number, directiveRef?: any | null): StylingContext {
|
||||
attrs: TAttributes, stylingStartIndex: number, directiveIndex: number = 0): StylingContext {
|
||||
const context = createEmptyStylingContext();
|
||||
patchContextWithStaticAttrs(context, attrs, stylingStartIndex, directiveRef);
|
||||
patchContextWithStaticAttrs(context, attrs, stylingStartIndex, directiveIndex);
|
||||
return context;
|
||||
}
|
||||
|
||||
@ -57,25 +57,14 @@ export function initializeStaticContext(
|
||||
* assigned to the context
|
||||
* @param attrsStylingStartIndex what index to start iterating within the
|
||||
* provided `attrs` array to start reading style and class values
|
||||
* @param directiveRef the directive instance with which static data is associated with.
|
||||
*/
|
||||
export function patchContextWithStaticAttrs(
|
||||
context: StylingContext, attrs: TAttributes, attrsStylingStartIndex: number,
|
||||
directiveRef?: any | null): void {
|
||||
directiveIndex: number): void {
|
||||
// this means the context has already been set and instantiated
|
||||
if (context[StylingIndex.MasterFlagPosition] & StylingFlags.BindingAllocationLocked) return;
|
||||
|
||||
// If the styling context has already been patched with the given directive's bindings,
|
||||
// then there is no point in doing it again. The reason why this may happen (the directive
|
||||
// styling being patched twice) is because the `stylingBinding` function is called each time
|
||||
// an element is created (both within a template function and within directive host bindings).
|
||||
const directives = context[StylingIndex.DirectiveRegistryPosition];
|
||||
let detectedIndex = getDirectiveRegistryValuesIndexOf(directives, directiveRef || null);
|
||||
if (detectedIndex === -1) {
|
||||
// this is a new directive which we have not seen yet.
|
||||
detectedIndex = allocateDirectiveIntoContext(context, directiveRef);
|
||||
}
|
||||
const directiveIndex = detectedIndex / DirectiveRegistryValuesIndex.Size;
|
||||
allocateOrUpdateDirectiveIntoContext(context, directiveIndex);
|
||||
|
||||
let initialClasses: InitialStylingValues|null = null;
|
||||
let initialStyles: InitialStylingValues|null = null;
|
||||
@ -199,7 +188,6 @@ export function allowNewBindingsForStylingContext(context: StylingContext): bool
|
||||
* reference the provided directive.
|
||||
*
|
||||
* @param context the existing styling context
|
||||
* @param directiveRef the directive that the new bindings will reference
|
||||
* @param classBindingNames an array of class binding names that will be added to the context
|
||||
* @param styleBindingNames an array of style binding names that will be added to the context
|
||||
* @param styleSanitizer an optional sanitizer that handle all sanitization on for each of
|
||||
@ -207,13 +195,14 @@ export function allowNewBindingsForStylingContext(context: StylingContext): bool
|
||||
* instance will only be active if and when the directive updates the bindings that it owns.
|
||||
*/
|
||||
export function updateContextWithBindings(
|
||||
context: StylingContext, directiveRef: any | null, classBindingNames?: string[] | null,
|
||||
context: StylingContext, directiveIndex: number, classBindingNames?: string[] | null,
|
||||
styleBindingNames?: string[] | null, styleSanitizer?: StyleSanitizeFn | null) {
|
||||
if (context[StylingIndex.MasterFlagPosition] & StylingFlags.BindingAllocationLocked) return;
|
||||
|
||||
// this means the context has already been patched with the directive's bindings
|
||||
const directiveIndex = findOrPatchDirectiveIntoRegistry(context, directiveRef, styleSanitizer);
|
||||
if (directiveIndex === -1) {
|
||||
const isNewDirective =
|
||||
findOrPatchDirectiveIntoRegistry(context, directiveIndex, false, styleSanitizer);
|
||||
if (!isNewDirective) {
|
||||
// this means the directive has already been patched in ... No point in doing anything
|
||||
return;
|
||||
}
|
||||
@ -470,46 +459,22 @@ export function updateContextWithBindings(
|
||||
* Searches through the existing registry of directives
|
||||
*/
|
||||
export function findOrPatchDirectiveIntoRegistry(
|
||||
context: StylingContext, directiveRef: any, styleSanitizer?: StyleSanitizeFn | null) {
|
||||
const directiveRefs = context[StylingIndex.DirectiveRegistryPosition];
|
||||
const nextOffsetInsertionIndex = context[StylingIndex.SinglePropOffsetPositions].length;
|
||||
context: StylingContext, directiveIndex: number, staticModeOnly: boolean,
|
||||
styleSanitizer?: StyleSanitizeFn | null): boolean {
|
||||
const directiveRegistry = context[StylingIndex.DirectiveRegistryPosition];
|
||||
const index = directiveIndex * DirectiveRegistryValuesIndex.Size;
|
||||
const singlePropStartPosition = index + DirectiveRegistryValuesIndex.SinglePropValuesIndexOffset;
|
||||
|
||||
let directiveIndex: number;
|
||||
let detectedIndex = getDirectiveRegistryValuesIndexOf(directiveRefs, directiveRef);
|
||||
// this means that the directive has already been registered into the registry
|
||||
if (index < directiveRegistry.length &&
|
||||
(directiveRegistry[singlePropStartPosition] as number) >= 0)
|
||||
return false;
|
||||
|
||||
if (detectedIndex === -1) {
|
||||
detectedIndex = directiveRefs.length;
|
||||
directiveIndex = directiveRefs.length / DirectiveRegistryValuesIndex.Size;
|
||||
|
||||
allocateDirectiveIntoContext(context, directiveRef);
|
||||
directiveRefs[detectedIndex + DirectiveRegistryValuesIndex.SinglePropValuesIndexOffset] =
|
||||
nextOffsetInsertionIndex;
|
||||
directiveRefs[detectedIndex + DirectiveRegistryValuesIndex.StyleSanitizerOffset] =
|
||||
styleSanitizer || null;
|
||||
} else {
|
||||
const singlePropStartPosition =
|
||||
detectedIndex + DirectiveRegistryValuesIndex.SinglePropValuesIndexOffset;
|
||||
if (directiveRefs[singlePropStartPosition] ! >= 0) {
|
||||
// the directive has already been patched into the context
|
||||
return -1;
|
||||
}
|
||||
|
||||
directiveIndex = detectedIndex / DirectiveRegistryValuesIndex.Size;
|
||||
|
||||
// because the directive already existed this means that it was set during elementHostAttrs or
|
||||
// elementStart which means that the binding values were not here. Therefore, the values below
|
||||
// need to be applied so that single class and style properties can be assigned later.
|
||||
const singlePropPositionIndex =
|
||||
detectedIndex + DirectiveRegistryValuesIndex.SinglePropValuesIndexOffset;
|
||||
directiveRefs[singlePropPositionIndex] = nextOffsetInsertionIndex;
|
||||
|
||||
// the sanitizer is also apart of the binding process and will be used when bindings are
|
||||
// applied.
|
||||
const styleSanitizerIndex = detectedIndex + DirectiveRegistryValuesIndex.StyleSanitizerOffset;
|
||||
directiveRefs[styleSanitizerIndex] = styleSanitizer || null;
|
||||
}
|
||||
|
||||
return directiveIndex;
|
||||
const singlePropsStartIndex =
|
||||
staticModeOnly ? -1 : context[StylingIndex.SinglePropOffsetPositions].length;
|
||||
allocateOrUpdateDirectiveIntoContext(
|
||||
context, directiveIndex, singlePropsStartIndex, styleSanitizer);
|
||||
return true;
|
||||
}
|
||||
|
||||
function getMatchingBindingIndex(
|
||||
@ -543,18 +508,13 @@ function getMatchingBindingIndex(
|
||||
* newly provided style values.
|
||||
* @param classesInput The key/value map of CSS class names that will be used for the update.
|
||||
* @param stylesInput The key/value map of CSS styles that will be used for the update.
|
||||
* @param directiveRef an optional reference to the directive responsible
|
||||
* for this binding change. If present then style binding will only
|
||||
* actualize if the directive has ownership over this binding
|
||||
* (see styling.ts#directives for more information about the algorithm).
|
||||
*/
|
||||
export function updateStylingMap(
|
||||
context: StylingContext, classesInput: {[key: string]: any} | string |
|
||||
BoundPlayerFactory<null|string|{[key: string]: any}>| null,
|
||||
stylesInput?: {[key: string]: any} | BoundPlayerFactory<null|{[key: string]: any}>| null,
|
||||
directiveRef?: any): void {
|
||||
const directiveIndex = getDirectiveIndexFromRegistry(context, directiveRef || null);
|
||||
|
||||
directiveIndex: number = 0): void {
|
||||
ngDevMode && assertValidDirectiveIndex(context, directiveIndex);
|
||||
classesInput = classesInput || null;
|
||||
stylesInput = stylesInput || null;
|
||||
const ignoreAllClassUpdates = isMultiValueCacheHit(context, true, directiveIndex, classesInput);
|
||||
@ -887,7 +847,6 @@ function patchStylingMapIntoContext(
|
||||
|
||||
if (dirty) {
|
||||
setContextDirty(context, true);
|
||||
setDirectiveDirty(context, directiveIndex, true);
|
||||
}
|
||||
|
||||
return totalNewAllocatedSlots;
|
||||
@ -901,18 +860,14 @@ function patchStylingMapIntoContext(
|
||||
* newly provided class value.
|
||||
* @param offset The index of the CSS class which is being updated.
|
||||
* @param addOrRemove Whether or not to add or remove the CSS class
|
||||
* @param directiveRef an optional reference to the directive responsible
|
||||
* for this binding change. If present then style binding will only
|
||||
* actualize if the directive has ownership over this binding
|
||||
* (see styling.ts#directives for more information about the algorithm).
|
||||
* @param forceOverride whether or not to skip all directive prioritization
|
||||
* and just apply the value regardless.
|
||||
*/
|
||||
export function updateClassProp(
|
||||
context: StylingContext, offset: number,
|
||||
input: boolean | BoundPlayerFactory<boolean|null>| null, directiveRef?: any,
|
||||
input: boolean | BoundPlayerFactory<boolean|null>| null, directiveIndex: number = 0,
|
||||
forceOverride?: boolean): void {
|
||||
updateSingleStylingValue(context, offset, input, true, directiveRef, forceOverride);
|
||||
updateSingleStylingValue(context, offset, input, true, directiveIndex, forceOverride);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -928,25 +883,21 @@ export function updateClassProp(
|
||||
* newly provided style value.
|
||||
* @param offset The index of the property which is being updated.
|
||||
* @param value The CSS style value that will be assigned
|
||||
* @param directiveRef an optional reference to the directive responsible
|
||||
* for this binding change. If present then style binding will only
|
||||
* actualize if the directive has ownership over this binding
|
||||
* (see styling.ts#directives for more information about the algorithm).
|
||||
* @param forceOverride whether or not to skip all directive prioritization
|
||||
* and just apply the value regardless.
|
||||
*/
|
||||
export function updateStyleProp(
|
||||
context: StylingContext, offset: number,
|
||||
input: string | boolean | null | BoundPlayerFactory<string|boolean|null>, directiveRef?: any,
|
||||
forceOverride?: boolean): void {
|
||||
updateSingleStylingValue(context, offset, input, false, directiveRef, forceOverride);
|
||||
input: string | boolean | null | BoundPlayerFactory<string|boolean|null>,
|
||||
directiveIndex: number = 0, forceOverride?: boolean): void {
|
||||
updateSingleStylingValue(context, offset, input, false, directiveIndex, forceOverride);
|
||||
}
|
||||
|
||||
function updateSingleStylingValue(
|
||||
context: StylingContext, offset: number,
|
||||
input: string | boolean | null | BoundPlayerFactory<string|boolean|null>, isClassBased: boolean,
|
||||
directiveRef: any, forceOverride?: boolean): void {
|
||||
const directiveIndex = getDirectiveIndexFromRegistry(context, directiveRef || null);
|
||||
directiveIndex: number, forceOverride?: boolean): void {
|
||||
ngDevMode && assertValidDirectiveIndex(context, directiveIndex);
|
||||
const singleIndex = getSinglePropIndexValue(context, directiveIndex, offset, isClassBased);
|
||||
const currValue = getValue(context, singleIndex);
|
||||
const currFlag = getPointers(context, singleIndex);
|
||||
@ -1001,7 +952,6 @@ function updateSingleStylingValue(
|
||||
|
||||
setDirty(context, indexForMulti, multiDirty);
|
||||
setDirty(context, singleIndex, singleDirty);
|
||||
setDirectiveDirty(context, directiveIndex, true);
|
||||
setContextDirty(context, true);
|
||||
}
|
||||
|
||||
@ -1029,120 +979,128 @@ function updateSingleStylingValue(
|
||||
* to this key/value map instead of being renderered via the renderer.
|
||||
* @param stylesStore if provided, the updated style values will be applied
|
||||
* to this key/value map instead of being renderered via the renderer.
|
||||
* @param directiveRef an optional directive that will be used to target which
|
||||
* styling values are rendered. If left empty, only the bindings that are
|
||||
* registered on the template will be rendered.
|
||||
* @returns number the total amount of players that got queued for animation (if any)
|
||||
*/
|
||||
export function renderStyling(
|
||||
context: StylingContext, renderer: Renderer3, rootOrView: RootContext | LView,
|
||||
context: StylingContext, renderer: Renderer3 | null, rootOrView: RootContext | LView,
|
||||
isFirstRender: boolean, classesStore?: BindingStore | null, stylesStore?: BindingStore | null,
|
||||
directiveRef?: any): number {
|
||||
directiveIndex: number = 0): number {
|
||||
let totalPlayersQueued = 0;
|
||||
const targetDirectiveIndex = getDirectiveIndexFromRegistry(context, directiveRef || null);
|
||||
|
||||
if (isContextDirty(context) && isDirectiveDirty(context, targetDirectiveIndex)) {
|
||||
const flushPlayerBuilders: any =
|
||||
context[StylingIndex.MasterFlagPosition] & StylingFlags.PlayerBuildersDirty;
|
||||
const native = context[StylingIndex.ElementPosition] !;
|
||||
const multiStartIndex = getMultiStylesStartIndex(context);
|
||||
// this prevents multiple attempts to render style/class values on
|
||||
// the same element...
|
||||
if (allowHostInstructionsQueueFlush(context, directiveIndex)) {
|
||||
// all styling instructions present within any hostBindings functions
|
||||
// do not update the context immediately when called. They are instead
|
||||
// queued up and applied to the context right at this point. Why? This
|
||||
// is because Angular evaluates component/directive and directive
|
||||
// sub-class code at different points and it's important that the
|
||||
// styling values are applied to the context in the right order
|
||||
// (see `interfaces/styling.ts` for more information).
|
||||
flushHostInstructionsQueue(context);
|
||||
|
||||
let stillDirty = false;
|
||||
for (let i = StylingIndex.SingleStylesStartPosition; i < context.length;
|
||||
i += StylingIndex.Size) {
|
||||
// there is no point in rendering styles that have not changed on screen
|
||||
if (isDirty(context, i)) {
|
||||
const flag = getPointers(context, i);
|
||||
const directiveIndex = getDirectiveIndexFromEntry(context, i);
|
||||
if (targetDirectiveIndex !== directiveIndex) {
|
||||
stillDirty = true;
|
||||
continue;
|
||||
}
|
||||
if (isContextDirty(context)) {
|
||||
// this is here to prevent things like <ng-container [style] [class]>...</ng-container>
|
||||
// or if there are any host style or class bindings present in a directive set on
|
||||
// a container node
|
||||
const native = context[StylingIndex.ElementPosition] !as HTMLElement;
|
||||
|
||||
const prop = getProp(context, i);
|
||||
const value = getValue(context, i);
|
||||
const styleSanitizer =
|
||||
(flag & StylingFlags.Sanitize) ? getStyleSanitizer(context, directiveIndex) : null;
|
||||
const playerBuilder = getPlayerBuilder(context, i);
|
||||
const isClassBased = flag & StylingFlags.Class ? true : false;
|
||||
const isInSingleRegion = i < multiStartIndex;
|
||||
const flushPlayerBuilders: any =
|
||||
context[StylingIndex.MasterFlagPosition] & StylingFlags.PlayerBuildersDirty;
|
||||
const multiStartIndex = getMultiStylesStartIndex(context);
|
||||
|
||||
let valueToApply: string|boolean|null = value;
|
||||
for (let i = StylingIndex.SingleStylesStartPosition; i < context.length;
|
||||
i += StylingIndex.Size) {
|
||||
// there is no point in rendering styles that have not changed on screen
|
||||
if (isDirty(context, i)) {
|
||||
const flag = getPointers(context, i);
|
||||
const directiveIndex = getDirectiveIndexFromEntry(context, i);
|
||||
const prop = getProp(context, i);
|
||||
const value = getValue(context, i);
|
||||
const styleSanitizer =
|
||||
(flag & StylingFlags.Sanitize) ? getStyleSanitizer(context, directiveIndex) : null;
|
||||
const playerBuilder = getPlayerBuilder(context, i);
|
||||
const isClassBased = flag & StylingFlags.Class ? true : false;
|
||||
const isInSingleRegion = i < multiStartIndex;
|
||||
|
||||
// VALUE DEFER CASE 1: Use a multi value instead of a null single value
|
||||
// this check implies that a single value was removed and we
|
||||
// should now defer to a multi value and use that (if set).
|
||||
if (isInSingleRegion && !valueExists(valueToApply, isClassBased)) {
|
||||
// single values ALWAYS have a reference to a multi index
|
||||
const multiIndex = getMultiOrSingleIndex(flag);
|
||||
valueToApply = getValue(context, multiIndex);
|
||||
}
|
||||
let valueToApply: string|boolean|null = value;
|
||||
|
||||
// VALUE DEFER CASE 2: Use the initial value if all else fails (is falsy)
|
||||
// the initial value will always be a string or null,
|
||||
// therefore we can safely adopt it in case there's nothing else
|
||||
// note that this should always be a falsy check since `false` is used
|
||||
// for both class and style comparisons (styles can't be false and false
|
||||
// classes are turned off and should therefore defer to their initial values)
|
||||
// Note that we ignore class-based deferals because otherwise a class can never
|
||||
// be removed in the case that it exists as true in the initial classes list...
|
||||
if (!valueExists(valueToApply, isClassBased)) {
|
||||
valueToApply = getInitialValue(context, flag);
|
||||
}
|
||||
|
||||
// if the first render is true then we do not want to start applying falsy
|
||||
// values to the DOM element's styling. Otherwise then we know there has
|
||||
// been a change and even if it's falsy then it's removing something that
|
||||
// was truthy before.
|
||||
const doApplyValue = isFirstRender ? valueToApply : true;
|
||||
if (doApplyValue) {
|
||||
if (isClassBased) {
|
||||
setClass(
|
||||
native, prop, valueToApply ? true : false, renderer, classesStore, playerBuilder);
|
||||
} else {
|
||||
setStyle(
|
||||
native, prop, valueToApply as string | null, renderer, styleSanitizer, stylesStore,
|
||||
playerBuilder);
|
||||
// VALUE DEFER CASE 1: Use a multi value instead of a null single value
|
||||
// this check implies that a single value was removed and we
|
||||
// should now defer to a multi value and use that (if set).
|
||||
if (isInSingleRegion && !valueExists(valueToApply, isClassBased)) {
|
||||
// single values ALWAYS have a reference to a multi index
|
||||
const multiIndex = getMultiOrSingleIndex(flag);
|
||||
valueToApply = getValue(context, multiIndex);
|
||||
}
|
||||
}
|
||||
|
||||
setDirty(context, i, false);
|
||||
}
|
||||
}
|
||||
// VALUE DEFER CASE 2: Use the initial value if all else fails (is falsy)
|
||||
// the initial value will always be a string or null,
|
||||
// therefore we can safely adopt it in case there's nothing else
|
||||
// note that this should always be a falsy check since `false` is used
|
||||
// for both class and style comparisons (styles can't be false and false
|
||||
// classes are turned off and should therefore defer to their initial values)
|
||||
// Note that we ignore class-based deferals because otherwise a class can never
|
||||
// be removed in the case that it exists as true in the initial classes list...
|
||||
if (!valueExists(valueToApply, isClassBased)) {
|
||||
valueToApply = getInitialValue(context, flag);
|
||||
}
|
||||
|
||||
if (flushPlayerBuilders) {
|
||||
const rootContext =
|
||||
Array.isArray(rootOrView) ? getRootContext(rootOrView) : rootOrView as RootContext;
|
||||
const playerContext = getPlayerContext(context) !;
|
||||
const playersStartIndex = playerContext[PlayerIndex.NonBuilderPlayersStart];
|
||||
for (let i = PlayerIndex.PlayerBuildersStartPosition; i < playersStartIndex;
|
||||
i += PlayerIndex.PlayerAndPlayerBuildersTupleSize) {
|
||||
const builder = playerContext[i] as ClassAndStylePlayerBuilder<any>| null;
|
||||
const playerInsertionIndex = i + PlayerIndex.PlayerOffsetPosition;
|
||||
const oldPlayer = playerContext[playerInsertionIndex] as Player | null;
|
||||
if (builder) {
|
||||
const player = builder.buildPlayer(oldPlayer, isFirstRender);
|
||||
if (player !== undefined) {
|
||||
if (player != null) {
|
||||
const wasQueued = addPlayerInternal(
|
||||
playerContext, rootContext, native as HTMLElement, player, playerInsertionIndex);
|
||||
wasQueued && totalPlayersQueued++;
|
||||
}
|
||||
if (oldPlayer) {
|
||||
oldPlayer.destroy();
|
||||
// if the first render is true then we do not want to start applying falsy
|
||||
// values to the DOM element's styling. Otherwise then we know there has
|
||||
// been a change and even if it's falsy then it's removing something that
|
||||
// was truthy before.
|
||||
const doApplyValue = renderer && (isFirstRender ? valueToApply : true);
|
||||
if (doApplyValue) {
|
||||
if (isClassBased) {
|
||||
setClass(
|
||||
native, prop, valueToApply ? true : false, renderer !, classesStore,
|
||||
playerBuilder);
|
||||
} else {
|
||||
setStyle(
|
||||
native, prop, valueToApply as string | null, renderer !, styleSanitizer,
|
||||
stylesStore, playerBuilder);
|
||||
}
|
||||
}
|
||||
} else if (oldPlayer) {
|
||||
// the player builder has been removed ... therefore we should delete the associated
|
||||
// player
|
||||
oldPlayer.destroy();
|
||||
|
||||
setDirty(context, i, false);
|
||||
}
|
||||
}
|
||||
setContextPlayersDirty(context, false);
|
||||
}
|
||||
|
||||
setDirectiveDirty(context, targetDirectiveIndex, false);
|
||||
setContextDirty(context, stillDirty);
|
||||
if (flushPlayerBuilders) {
|
||||
const rootContext =
|
||||
Array.isArray(rootOrView) ? getRootContext(rootOrView) : rootOrView as RootContext;
|
||||
const playerContext = getPlayerContext(context) !;
|
||||
const playersStartIndex = playerContext[PlayerIndex.NonBuilderPlayersStart];
|
||||
for (let i = PlayerIndex.PlayerBuildersStartPosition; i < playersStartIndex;
|
||||
i += PlayerIndex.PlayerAndPlayerBuildersTupleSize) {
|
||||
const builder = playerContext[i] as ClassAndStylePlayerBuilder<any>| null;
|
||||
const playerInsertionIndex = i + PlayerIndex.PlayerOffsetPosition;
|
||||
const oldPlayer = playerContext[playerInsertionIndex] as Player | null;
|
||||
if (builder) {
|
||||
const player = builder.buildPlayer(oldPlayer, isFirstRender);
|
||||
if (player !== undefined) {
|
||||
if (player != null) {
|
||||
const wasQueued = addPlayerInternal(
|
||||
playerContext, rootContext, native as HTMLElement, player,
|
||||
playerInsertionIndex);
|
||||
wasQueued && totalPlayersQueued++;
|
||||
}
|
||||
if (oldPlayer) {
|
||||
oldPlayer.destroy();
|
||||
}
|
||||
}
|
||||
} else if (oldPlayer) {
|
||||
// the player builder has been removed ... therefore we should delete the associated
|
||||
// player
|
||||
oldPlayer.destroy();
|
||||
}
|
||||
}
|
||||
setContextPlayersDirty(context, false);
|
||||
}
|
||||
|
||||
setContextDirty(context, false);
|
||||
}
|
||||
}
|
||||
|
||||
return totalPlayersQueued;
|
||||
@ -1622,43 +1580,6 @@ export function getDirectiveIndexFromEntry(context: StylingContext, index: numbe
|
||||
return value & DirectiveOwnerAndPlayerBuilderIndex.BitMask;
|
||||
}
|
||||
|
||||
function getDirectiveIndexFromRegistry(context: StylingContext, directiveRef: any) {
|
||||
let directiveIndex: number;
|
||||
|
||||
const dirs = context[StylingIndex.DirectiveRegistryPosition];
|
||||
let index = getDirectiveRegistryValuesIndexOf(dirs, directiveRef);
|
||||
if (index === -1) {
|
||||
// if the directive was not allocated then this means that styling is
|
||||
// being applied in a dynamic way AFTER the element was already instantiated
|
||||
index = dirs.length;
|
||||
directiveIndex = index > 0 ? index / DirectiveRegistryValuesIndex.Size : 0;
|
||||
|
||||
dirs.push(null, null, null, null);
|
||||
dirs[index + DirectiveRegistryValuesIndex.DirectiveValueOffset] = directiveRef;
|
||||
dirs[index + DirectiveRegistryValuesIndex.DirtyFlagOffset] = false;
|
||||
dirs[index + DirectiveRegistryValuesIndex.SinglePropValuesIndexOffset] = -1;
|
||||
|
||||
const classesStartIndex =
|
||||
getMultiClassesStartIndex(context) || StylingIndex.SingleStylesStartPosition;
|
||||
registerMultiMapEntry(context, directiveIndex, true, context.length);
|
||||
registerMultiMapEntry(context, directiveIndex, false, classesStartIndex);
|
||||
} else {
|
||||
directiveIndex = index > 0 ? index / DirectiveRegistryValuesIndex.Size : 0;
|
||||
}
|
||||
|
||||
return directiveIndex;
|
||||
}
|
||||
|
||||
function getDirectiveRegistryValuesIndexOf(
|
||||
directives: DirectiveRegistryValues, directive: {}): number {
|
||||
for (let i = 0; i < directives.length; i += DirectiveRegistryValuesIndex.Size) {
|
||||
if (directives[i] === directive) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function getInitialStylingValuesIndexOf(keyValues: InitialStylingValues, key: string): number {
|
||||
for (let i = InitialStylingValuesIndex.KeyValueStartPosition; i < keyValues.length;
|
||||
i += InitialStylingValuesIndex.Size) {
|
||||
@ -1724,21 +1645,6 @@ function getStyleSanitizer(context: StylingContext, directiveIndex: number): Sty
|
||||
return value as StyleSanitizeFn | null;
|
||||
}
|
||||
|
||||
function isDirectiveDirty(context: StylingContext, directiveIndex: number): boolean {
|
||||
const dirs = context[StylingIndex.DirectiveRegistryPosition];
|
||||
return dirs
|
||||
[directiveIndex * DirectiveRegistryValuesIndex.Size +
|
||||
DirectiveRegistryValuesIndex.DirtyFlagOffset] as boolean;
|
||||
}
|
||||
|
||||
function setDirectiveDirty(
|
||||
context: StylingContext, directiveIndex: number, dirtyYes: boolean): void {
|
||||
const dirs = context[StylingIndex.DirectiveRegistryPosition];
|
||||
dirs
|
||||
[directiveIndex * DirectiveRegistryValuesIndex.Size +
|
||||
DirectiveRegistryValuesIndex.DirtyFlagOffset] = dirtyYes;
|
||||
}
|
||||
|
||||
function allowValueChange(
|
||||
currentValue: string | boolean | null, newValue: string | boolean | null,
|
||||
currentDirectiveOwner: number, newDirectiveOwner: number) {
|
||||
@ -1994,3 +1900,12 @@ function addOrUpdateStaticStyle(
|
||||
staticStyles[index + InitialStylingValuesIndex.DirectiveOwnerOffset] = directiveOwnerIndex;
|
||||
return index;
|
||||
}
|
||||
|
||||
function assertValidDirectiveIndex(context: StylingContext, directiveIndex: number) {
|
||||
const dirs = context[StylingIndex.DirectiveRegistryPosition];
|
||||
const index = directiveIndex * DirectiveRegistryValuesIndex.Size;
|
||||
if (index >= dirs.length ||
|
||||
dirs[index + DirectiveRegistryValuesIndex.SinglePropValuesIndexOffset] === -1) {
|
||||
throw new Error('The provided directive is not registered with the styling context');
|
||||
}
|
||||
}
|
95
packages/core/src/render3/styling/host_instructions_queue.ts
Normal file
95
packages/core/src/render3/styling/host_instructions_queue.ts
Normal file
@ -0,0 +1,95 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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.io/license
|
||||
*/
|
||||
import {HostInstructionsQueue, HostInstructionsQueueIndex, StylingContext, StylingIndex} from '../interfaces/styling';
|
||||
import {DEFAULT_TEMPLATE_DIRECTIVE_INDEX} from '../styling/shared';
|
||||
|
||||
/*
|
||||
* This file contains the logic to defer all hostBindings-related styling code to run
|
||||
* at a later point, instead of immediately (as is the case with how template-level
|
||||
* styling instructions are run).
|
||||
*
|
||||
* Certain styling instructions, present within directives, components and sub-classed
|
||||
* directives, are evaluated at different points (depending on priority) and will therefore
|
||||
* not be applied to the styling context of an element immediately. They are instead
|
||||
* designed to be applied just before styling is applied to an element.
|
||||
*
|
||||
* (The priority for when certain host-related styling operations are executed is discussed
|
||||
* more within `interfaces/styling.ts`.)
|
||||
*/
|
||||
|
||||
export function registerHostDirective(context: StylingContext, directiveIndex: number) {
|
||||
let buffer = context[StylingIndex.HostInstructionsQueue];
|
||||
if (!buffer) {
|
||||
buffer = context[StylingIndex.HostInstructionsQueue] = [DEFAULT_TEMPLATE_DIRECTIVE_INDEX];
|
||||
}
|
||||
buffer[HostInstructionsQueueIndex.LastRegisteredDirectiveIndexPosition] = directiveIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a styling instruction to be run just before `renderStyling()` is executed.
|
||||
*/
|
||||
export function enqueueHostInstruction<T extends Function>(
|
||||
context: StylingContext, priority: number, instructionFn: T, instructionFnArgs: ParamsOf<T>) {
|
||||
const buffer: HostInstructionsQueue = context[StylingIndex.HostInstructionsQueue] !;
|
||||
const index = findNextInsertionIndex(buffer, priority);
|
||||
buffer.splice(index, 0, priority, instructionFn, instructionFnArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Figures out where exactly to to insert the next host instruction queue entry.
|
||||
*/
|
||||
function findNextInsertionIndex(buffer: HostInstructionsQueue, priority: number): number {
|
||||
for (let i = HostInstructionsQueueIndex.ValuesStartPosition; i < buffer.length;
|
||||
i += HostInstructionsQueueIndex.Size) {
|
||||
const p = buffer[i + HostInstructionsQueueIndex.DirectiveIndexOffset] as number;
|
||||
if (p > priority) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return buffer.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through the host instructions queue (if present within the provided
|
||||
* context) and executes each queued instruction entry.
|
||||
*/
|
||||
export function flushQueue(context: StylingContext): void {
|
||||
const buffer = context[StylingIndex.HostInstructionsQueue];
|
||||
if (buffer) {
|
||||
for (let i = HostInstructionsQueueIndex.ValuesStartPosition; i < buffer.length;
|
||||
i += HostInstructionsQueueIndex.Size) {
|
||||
const fn = buffer[i + HostInstructionsQueueIndex.InstructionFnOffset] as Function;
|
||||
const args = buffer[i + HostInstructionsQueueIndex.ParamsOffset] as any[];
|
||||
fn(...args);
|
||||
}
|
||||
buffer.length = HostInstructionsQueueIndex.ValuesStartPosition;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not to allow the host instructions queue to be flushed or not.
|
||||
*
|
||||
* Because the hostBindings function code is unaware of the presence of other host bindings
|
||||
* (as well as the template function) then styling is evaluated multiple times per element.
|
||||
* To prevent style and class values from being applied to the element multiple times, a
|
||||
* flush is only allowed when the last directive (the directive that was registered into
|
||||
* the styling context) attempts to render its styling.
|
||||
*/
|
||||
export function allowFlush(context: StylingContext, directiveIndex: number): boolean {
|
||||
const buffer = context[StylingIndex.HostInstructionsQueue];
|
||||
if (buffer) {
|
||||
return buffer[HostInstructionsQueueIndex.LastRegisteredDirectiveIndexPosition] ===
|
||||
directiveIndex;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Infers the parameters of a given function into a typed array.
|
||||
*/
|
||||
export type ParamsOf<T> = T extends(...args: infer T) => any ? T : never;
|
17
packages/core/src/render3/styling/shared.ts
Normal file
17
packages/core/src/render3/styling/shared.ts
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* The default directive styling index value for template-based bindings.
|
||||
*
|
||||
* All host-level bindings (e.g. `hostStyleProp` and `hostStylingMap`) are
|
||||
* assigned a directive styling index value based on the current directive
|
||||
* uniqueId and the directive super-class inheritance depth. But for template
|
||||
* bindings they always have the same directive styling index value.
|
||||
*/
|
||||
export const DEFAULT_TEMPLATE_DIRECTIVE_INDEX = 0;
|
@ -19,6 +19,7 @@ import {HEADER_OFFSET, HOST, LView, RootContext} from '../interfaces/view';
|
||||
import {getTNode, isStylingContext} from '../util/view_utils';
|
||||
|
||||
import {CorePlayerHandler} from './core_player_handler';
|
||||
import {DEFAULT_TEMPLATE_DIRECTIVE_INDEX} from './shared';
|
||||
|
||||
export const ANIMATION_PROP_PREFIX = '@';
|
||||
|
||||
@ -35,12 +36,13 @@ export function createEmptyStylingContext(
|
||||
[0, 0], // SinglePropOffsets
|
||||
[0], // CachedMultiClassValue
|
||||
[0], // CachedMultiStyleValue
|
||||
null, // HostBuffer
|
||||
null, // PlayerContext
|
||||
];
|
||||
|
||||
// whenever a context is created there is always a `null` directive
|
||||
// that is registered (which is a placeholder for the "template").
|
||||
allocateDirectiveIntoContext(context, null);
|
||||
allocateOrUpdateDirectiveIntoContext(context, DEFAULT_TEMPLATE_DIRECTIVE_INDEX);
|
||||
return context;
|
||||
}
|
||||
|
||||
@ -60,25 +62,28 @@ export function createEmptyStylingContext(
|
||||
* @param directiveRef the directive that will be allocated into the context
|
||||
* @returns the index where the directive was inserted into
|
||||
*/
|
||||
export function allocateDirectiveIntoContext(
|
||||
context: StylingContext, directiveRef: any | null): number {
|
||||
// this is a new directive which we have not seen yet.
|
||||
const dirs = context[StylingIndex.DirectiveRegistryPosition];
|
||||
const i = dirs.length;
|
||||
export function allocateOrUpdateDirectiveIntoContext(
|
||||
context: StylingContext, directiveIndex: number, singlePropValuesIndex: number = -1,
|
||||
styleSanitizer?: StyleSanitizeFn | null | undefined): void {
|
||||
const directiveRegistry = context[StylingIndex.DirectiveRegistryPosition];
|
||||
|
||||
const index = directiveIndex * DirectiveRegistryValuesIndex.Size;
|
||||
// we preemptively make space into the directives array and then
|
||||
// assign values slot-by-slot to ensure that if the directive ordering
|
||||
// changes then it will still function
|
||||
dirs.push(null, null, null, null);
|
||||
dirs[i + DirectiveRegistryValuesIndex.DirectiveValueOffset] = directiveRef;
|
||||
dirs[i + DirectiveRegistryValuesIndex.DirtyFlagOffset] = false;
|
||||
dirs[i + DirectiveRegistryValuesIndex.StyleSanitizerOffset] = null;
|
||||
const limit = index + DirectiveRegistryValuesIndex.Size;
|
||||
for (let i = directiveRegistry.length; i < limit; i += DirectiveRegistryValuesIndex.Size) {
|
||||
// -1 is used to signal that the directive has been allocated, but
|
||||
// no actual style or class bindings have been registered yet...
|
||||
directiveRegistry.push(-1, null);
|
||||
}
|
||||
|
||||
// -1 is used to signal that the directive has been allocated, but
|
||||
// no actual style or class bindings have been registered yet...
|
||||
dirs[i + DirectiveRegistryValuesIndex.SinglePropValuesIndexOffset] = -1;
|
||||
|
||||
return i;
|
||||
const propValuesStartPosition = index + DirectiveRegistryValuesIndex.SinglePropValuesIndexOffset;
|
||||
if (singlePropValuesIndex >= 0 && directiveRegistry[propValuesStartPosition] === -1) {
|
||||
directiveRegistry[propValuesStartPosition] = singlePropValuesIndex;
|
||||
directiveRegistry[index + DirectiveRegistryValuesIndex.StyleSanitizerOffset] =
|
||||
styleSanitizer || null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user