@angular/core
Version:
Angular - the core framework
866 lines • 264 kB
JavaScript
import * as tslib_1 from "tslib";
import { EMPTY_ARRAY, EMPTY_OBJ } from '../empty';
import { RendererStyleFlags3, isProceduralRenderer } from '../interfaces/renderer';
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, allocateOrUpdateDirectiveIntoContext, createEmptyStylingContext, getPlayerContext } from './util';
/**
* This file includes the code to power all styling-binding operations in Angular.
*
* These include:
* [style]="myStyleObj"
* [class]="myClassObj"
* [style.prop]="myPropValue"
* [class.name]="myClassValue"
*
* It also includes code that will allow style binding code to operate within host
* bindings for components/directives.
*
* There are many different ways in which these functions below are called. Please see
* `render3/interfaces/styling.ts` to get a better idea of how the styling algorithm works.
*/
/**
* Creates a new StylingContext an fills it with the provided static styling attribute values.
*/
export function initializeStaticContext(attrs, stylingStartIndex, directiveIndex) {
if (directiveIndex === void 0) { directiveIndex = 0; }
var context = createEmptyStylingContext();
patchContextWithStaticAttrs(context, attrs, stylingStartIndex, directiveIndex);
return context;
}
/**
* Designed to update an existing styling context with new static styling
* data (classes and styles).
*
* @param context the existing styling context
* @param attrs an array of new static styling attributes that will be
* assigned to the context
* @param attrsStylingStartIndex what index to start iterating within the
* provided `attrs` array to start reading style and class values
*/
export function patchContextWithStaticAttrs(context, attrs, attrsStylingStartIndex, directiveIndex) {
// this means the context has already been set and instantiated
if (context[1 /* MasterFlagPosition */] & 16 /* BindingAllocationLocked */)
return;
allocateOrUpdateDirectiveIntoContext(context, directiveIndex);
var initialClasses = null;
var initialStyles = null;
var mode = -1;
for (var i = attrsStylingStartIndex; i < attrs.length; i++) {
var attr = attrs[i];
if (typeof attr == 'number') {
mode = attr;
}
else if (mode == 1 /* Classes */) {
initialClasses = initialClasses || context[4 /* InitialClassValuesPosition */];
patchInitialStylingValue(initialClasses, attr, true, directiveIndex);
}
else if (mode == 2 /* Styles */) {
initialStyles = initialStyles || context[3 /* InitialStyleValuesPosition */];
patchInitialStylingValue(initialStyles, attr, attrs[++i], directiveIndex);
}
}
}
/**
* Designed to add a style or class value into the existing set of initial styles.
*
* The function will search and figure out if a style/class value is already present
* within the provided initial styling array. If and when a style/class value is
* present (allocated) then the code below will set the new value depending on the
* following cases:
*
* 1) if the existing value is falsy (this happens because a `[class.prop]` or
* `[style.prop]` binding was set, but there wasn't a matching static style
* or class present on the context)
* 2) if the value was set already by the template, component or directive, but the
* new value is set on a higher level (i.e. a sub component which extends a parent
* component sets its value after the parent has already set the same one)
* 3) if the same directive provides a new set of styling values to set
*
* @param initialStyling the initial styling array where the new styling entry will be added to
* @param prop the property value of the new entry (e.g. `width` (styles) or `foo` (classes))
* @param value the styling value of the new entry (e.g. `absolute` (styles) or `true` (classes))
* @param directiveOwnerIndex the directive owner index value of the styling source responsible
* for these styles (see `interfaces/styling.ts#directives` for more info)
*/
function patchInitialStylingValue(initialStyling, prop, value, directiveOwnerIndex) {
for (var i = 2 /* KeyValueStartPosition */; i < initialStyling.length; i += 3 /* Size */) {
var key = initialStyling[i + 0 /* PropOffset */];
if (key === prop) {
var existingValue = initialStyling[i + 1 /* ValueOffset */];
var existingOwner = initialStyling[i + 2 /* DirectiveOwnerOffset */];
if (allowValueChange(existingValue, value, existingOwner, directiveOwnerIndex)) {
addOrUpdateStaticStyle(i, initialStyling, prop, value, directiveOwnerIndex);
}
return;
}
}
// We did not find existing key, add a new one.
addOrUpdateStaticStyle(null, initialStyling, prop, value, directiveOwnerIndex);
}
/**
* Runs through the initial class values present in the provided
* context and renders them via the provided renderer on the element.
*
* @param element the element the styling will be applied to
* @param context the source styling context which contains the initial class values
* @param renderer the renderer instance that will be used to apply the class
* @returns the index that the classes were applied up until
*/
export function renderInitialClasses(element, context, renderer, startIndex) {
var initialClasses = context[4 /* InitialClassValuesPosition */];
var i = startIndex || 2 /* KeyValueStartPosition */;
while (i < initialClasses.length) {
var value = initialClasses[i + 1 /* ValueOffset */];
if (value) {
setClass(element, initialClasses[i + 0 /* PropOffset */], true, renderer, null);
}
i += 3 /* Size */;
}
return i;
}
/**
* Runs through the initial styles values present in the provided
* context and renders them via the provided renderer on the element.
*
* @param element the element the styling will be applied to
* @param context the source styling context which contains the initial class values
* @param renderer the renderer instance that will be used to apply the class
* @returns the index that the styles were applied up until
*/
export function renderInitialStyles(element, context, renderer, startIndex) {
var initialStyles = context[3 /* InitialStyleValuesPosition */];
var i = startIndex || 2 /* KeyValueStartPosition */;
while (i < initialStyles.length) {
var value = initialStyles[i + 1 /* ValueOffset */];
if (value) {
setStyle(element, initialStyles[i + 0 /* PropOffset */], value, renderer, null);
}
i += 3 /* Size */;
}
return i;
}
export function allowNewBindingsForStylingContext(context) {
return (context[1 /* MasterFlagPosition */] & 16 /* BindingAllocationLocked */) === 0;
}
/**
* Adds in new binding values to a styling context.
*
* If a directive value is provided then all provided class/style binding names will
* reference the provided directive.
*
* @param context the existing styling context
* @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
* the bindings added to the context. Note that if a directive is provided then the sanitizer
* instance will only be active if and when the directive updates the bindings that it owns.
*/
export function updateContextWithBindings(context, directiveIndex, classBindingNames, styleBindingNames, styleSanitizer) {
if (context[1 /* MasterFlagPosition */] & 16 /* BindingAllocationLocked */)
return;
// this means the context has already been patched with the directive's bindings
var isNewDirective = findOrPatchDirectiveIntoRegistry(context, directiveIndex, false, styleSanitizer);
if (!isNewDirective) {
// this means the directive has already been patched in ... No point in doing anything
return;
}
if (styleBindingNames) {
styleBindingNames = hyphenateEntries(styleBindingNames);
}
// there are alot of variables being used below to track where in the context the new
// binding values will be placed. Because the context consists of multiple types of
// entries (single classes/styles and multi classes/styles) alot of the index positions
// need to be computed ahead of time and the context needs to be extended before the values
// are inserted in.
var singlePropOffsetValues = context[5 /* SinglePropOffsetPositions */];
var totalCurrentClassBindings = singlePropOffsetValues[1 /* ClassesCountPosition */];
var totalCurrentStyleBindings = singlePropOffsetValues[0 /* StylesCountPosition */];
var cachedClassMapValues = context[6 /* CachedMultiClasses */];
var cachedStyleMapValues = context[7 /* CachedMultiStyles */];
var classesOffset = totalCurrentClassBindings * 4 /* Size */;
var stylesOffset = totalCurrentStyleBindings * 4 /* Size */;
var singleStylesStartIndex = 10 /* SingleStylesStartPosition */;
var singleClassesStartIndex = singleStylesStartIndex + stylesOffset;
var multiStylesStartIndex = singleClassesStartIndex + classesOffset;
var multiClassesStartIndex = multiStylesStartIndex + stylesOffset;
// because we're inserting more bindings into the context, this means that the
// binding values need to be referenced the singlePropOffsetValues array so that
// the template/directive can easily find them inside of the `styleProp`
// and the `classProp` functions without iterating through the entire context.
// The first step to setting up these reference points is to mark how many bindings
// are being added. Even if these bindings already exist in the context, the directive
// or template code will still call them unknowingly. Therefore the total values need
// to be registered so that we know how many bindings are assigned to each directive.
var currentSinglePropsLength = singlePropOffsetValues.length;
singlePropOffsetValues.push(styleBindingNames ? styleBindingNames.length : 0, classBindingNames ? classBindingNames.length : 0);
// the code below will check to see if a new style binding already exists in the context
// if so then there is no point in inserting it into the context again. Whether or not it
// exists the styling offset code will now know exactly where it is
var insertionOffset = 0;
var filteredStyleBindingNames = [];
if (styleBindingNames && styleBindingNames.length) {
for (var i_1 = 0; i_1 < styleBindingNames.length; i_1++) {
var name_1 = styleBindingNames[i_1];
var singlePropIndex = getMatchingBindingIndex(context, name_1, singleStylesStartIndex, singleClassesStartIndex);
if (singlePropIndex == -1) {
singlePropIndex = singleClassesStartIndex + insertionOffset;
insertionOffset += 4 /* Size */;
filteredStyleBindingNames.push(name_1);
}
singlePropOffsetValues.push(singlePropIndex);
}
}
// just like with the style binding loop above, the new class bindings get the same treatment...
var filteredClassBindingNames = [];
if (classBindingNames && classBindingNames.length) {
for (var i_2 = 0; i_2 < classBindingNames.length; i_2++) {
var name_2 = classBindingNames[i_2];
var singlePropIndex = getMatchingBindingIndex(context, name_2, singleClassesStartIndex, multiStylesStartIndex);
if (singlePropIndex == -1) {
singlePropIndex = multiStylesStartIndex + insertionOffset;
insertionOffset += 4 /* Size */;
filteredClassBindingNames.push(name_2);
}
else {
singlePropIndex += filteredStyleBindingNames.length * 4 /* Size */;
}
singlePropOffsetValues.push(singlePropIndex);
}
}
// because new styles are being inserted, this means the existing collection of style offset
// index values are incorrect (they point to the wrong values). The code below will run through
// the entire offset array and update the existing set of index values to point to their new
// locations while taking the new binding values into consideration.
var i = 2 /* ValueStartPosition */;
if (filteredStyleBindingNames.length) {
while (i < currentSinglePropsLength) {
var totalStyles = singlePropOffsetValues[i + 0 /* StylesCountPosition */];
var totalClasses = singlePropOffsetValues[i + 1 /* ClassesCountPosition */];
if (totalClasses) {
var start = i + 2 /* ValueStartPosition */ + totalStyles;
for (var j = start; j < start + totalClasses; j++) {
singlePropOffsetValues[j] += filteredStyleBindingNames.length * 4 /* Size */;
}
}
var total = totalStyles + totalClasses;
i += 2 /* ValueStartPosition */ + total;
}
}
var totalNewEntries = filteredClassBindingNames.length + filteredStyleBindingNames.length;
// in the event that there are new style values being inserted, all existing class and style
// bindings need to have their pointer values offsetted with the new amount of space that is
// used for the new style/class bindings.
for (var i_3 = singleStylesStartIndex; i_3 < context.length; i_3 += 4 /* Size */) {
var isMultiBased = i_3 >= multiStylesStartIndex;
var isClassBased = i_3 >= (isMultiBased ? multiClassesStartIndex : singleClassesStartIndex);
var flag = getPointers(context, i_3);
var staticIndex = getInitialIndex(flag);
var singleOrMultiIndex = getMultiOrSingleIndex(flag);
if (isMultiBased) {
singleOrMultiIndex +=
isClassBased ? (filteredStyleBindingNames.length * 4 /* Size */) : 0;
}
else {
singleOrMultiIndex += (totalNewEntries * 4 /* Size */) +
((isClassBased ? filteredStyleBindingNames.length : 0) * 4 /* Size */);
}
setFlag(context, i_3, pointers(flag, staticIndex, singleOrMultiIndex));
}
// this is where we make space in the context for the new style bindings
for (var i_4 = 0; i_4 < filteredStyleBindingNames.length * 4 /* Size */; i_4++) {
context.splice(multiClassesStartIndex, 0, null);
context.splice(singleClassesStartIndex, 0, null);
singleClassesStartIndex++;
multiStylesStartIndex++;
multiClassesStartIndex += 2; // both single + multi slots were inserted
}
// this is where we make space in the context for the new class bindings
for (var i_5 = 0; i_5 < filteredClassBindingNames.length * 4 /* Size */; i_5++) {
context.splice(multiStylesStartIndex, 0, null);
context.push(null);
multiStylesStartIndex++;
multiClassesStartIndex++;
}
var initialClasses = context[4 /* InitialClassValuesPosition */];
var initialStyles = context[3 /* InitialStyleValuesPosition */];
// the code below will insert each new entry into the context and assign the appropriate
// flags and index values to them. It's important this runs at the end of this function
// because the context, property offset and index values have all been computed just before.
for (var i_6 = 0; i_6 < totalNewEntries; i_6++) {
var entryIsClassBased = i_6 >= filteredStyleBindingNames.length;
var adjustedIndex = entryIsClassBased ? (i_6 - filteredStyleBindingNames.length) : i_6;
var propName = entryIsClassBased ? filteredClassBindingNames[adjustedIndex] :
filteredStyleBindingNames[adjustedIndex];
var multiIndex = void 0, singleIndex = void 0;
if (entryIsClassBased) {
multiIndex = multiClassesStartIndex +
((totalCurrentClassBindings + adjustedIndex) * 4 /* Size */);
singleIndex = singleClassesStartIndex +
((totalCurrentClassBindings + adjustedIndex) * 4 /* Size */);
}
else {
multiIndex =
multiStylesStartIndex + ((totalCurrentStyleBindings + adjustedIndex) * 4 /* Size */);
singleIndex = singleStylesStartIndex +
((totalCurrentStyleBindings + adjustedIndex) * 4 /* Size */);
}
// if a property is not found in the initial style values list then it
// is ALWAYS added in case a follow-up directive introduces the same initial
// style/class value later on.
var initialValuesToLookup = entryIsClassBased ? initialClasses : initialStyles;
var indexForInitial = getInitialStylingValuesIndexOf(initialValuesToLookup, propName);
if (indexForInitial === -1) {
indexForInitial = addOrUpdateStaticStyle(null, initialValuesToLookup, propName, entryIsClassBased ? false : null, directiveIndex) +
1 /* ValueOffset */;
}
else {
indexForInitial += 1 /* ValueOffset */;
}
var initialFlag = prepareInitialFlag(context, propName, entryIsClassBased, styleSanitizer || null);
setFlag(context, singleIndex, pointers(initialFlag, indexForInitial, multiIndex));
setProp(context, singleIndex, propName);
setValue(context, singleIndex, null);
setPlayerBuilderIndex(context, singleIndex, 0, directiveIndex);
setFlag(context, multiIndex, pointers(initialFlag, indexForInitial, singleIndex));
setProp(context, multiIndex, propName);
setValue(context, multiIndex, null);
setPlayerBuilderIndex(context, multiIndex, 0, directiveIndex);
}
// the total classes/style values are updated so the next time the context is patched
// additional style/class bindings from another directive then it knows exactly where
// to insert them in the context
singlePropOffsetValues[1 /* ClassesCountPosition */] =
totalCurrentClassBindings + filteredClassBindingNames.length;
singlePropOffsetValues[0 /* StylesCountPosition */] =
totalCurrentStyleBindings + filteredStyleBindingNames.length;
// the map-based values also need to know how many entries got inserted
cachedClassMapValues[0 /* EntriesCountPosition */] +=
filteredClassBindingNames.length;
cachedStyleMapValues[0 /* EntriesCountPosition */] +=
filteredStyleBindingNames.length;
var newStylesSpaceAllocationSize = filteredStyleBindingNames.length * 4 /* Size */;
var newClassesSpaceAllocationSize = filteredClassBindingNames.length * 4 /* Size */;
// update the multi styles cache with a reference for the directive that was just inserted
var directiveMultiStylesStartIndex = multiStylesStartIndex + totalCurrentStyleBindings * 4 /* Size */;
var cachedStyleMapIndex = cachedStyleMapValues.length;
registerMultiMapEntry(context, directiveIndex, false, directiveMultiStylesStartIndex, filteredStyleBindingNames.length);
for (var i_7 = 1 /* ValuesStartPosition */; i_7 < cachedStyleMapIndex; i_7 += 4 /* Size */) {
// multi values start after all the single values (which is also where classes are) in the
// context therefore the new class allocation size should be taken into account
cachedStyleMapValues[i_7 + 1 /* PositionStartOffset */] +=
newClassesSpaceAllocationSize + newStylesSpaceAllocationSize;
}
// update the multi classes cache with a reference for the directive that was just inserted
var directiveMultiClassesStartIndex = multiClassesStartIndex + totalCurrentClassBindings * 4 /* Size */;
var cachedClassMapIndex = cachedClassMapValues.length;
registerMultiMapEntry(context, directiveIndex, true, directiveMultiClassesStartIndex, filteredClassBindingNames.length);
for (var i_8 = 1 /* ValuesStartPosition */; i_8 < cachedClassMapIndex; i_8 += 4 /* Size */) {
// the reason why both the styles + classes space is allocated to the existing offsets is
// because the styles show up before the classes in the context and any new inserted
// styles will offset any existing class entries in the context (even if there are no
// new class entries added) also the reason why it's *2 is because both single + multi
// entries for each new style have been added in the context before the multi class values
// actually start
cachedClassMapValues[i_8 + 1 /* PositionStartOffset */] +=
(newStylesSpaceAllocationSize * 2) + newClassesSpaceAllocationSize;
}
// there is no initial value flag for the master index since it doesn't
// reference an initial style value
var masterFlag = pointers(0, 0, multiStylesStartIndex);
setFlag(context, 1 /* MasterFlagPosition */, masterFlag);
}
/**
* Searches through the existing registry of directives
*/
export function findOrPatchDirectiveIntoRegistry(context, directiveIndex, staticModeOnly, styleSanitizer) {
var directiveRegistry = context[2 /* DirectiveRegistryPosition */];
var index = directiveIndex * 2 /* Size */;
var singlePropStartPosition = index + 0 /* SinglePropValuesIndexOffset */;
// this means that the directive has already been registered into the registry
if (index < directiveRegistry.length &&
directiveRegistry[singlePropStartPosition] >= 0)
return false;
var singlePropsStartIndex = staticModeOnly ? -1 : context[5 /* SinglePropOffsetPositions */].length;
allocateOrUpdateDirectiveIntoContext(context, directiveIndex, singlePropsStartIndex, styleSanitizer);
return true;
}
function getMatchingBindingIndex(context, bindingName, start, end) {
for (var j = start; j < end; j += 4 /* Size */) {
if (getProp(context, j) === bindingName)
return j;
}
return -1;
}
/**
* Registers the provided multi class values to the context.
*
* This function will iterate over the provided `classesInput` values and
* insert/update or remove them from the context at exactly the right spot.
*
* This function also takes in a directive which implies that the styling values will
* be evaluated for that directive with respect to any other styling that already exists
* on the context. When there are styles that conflict (e.g. say `ngClass` and `[class]`
* both update the `foo` className value at the same time) then the styling algorithm code below
* will decide which one wins based on the directive styling prioritization mechanism. (This
* mechanism is better explained in render3/interfaces/styling.ts#directives).
*
* This function will not render any styling values on screen, but is rather designed to
* prepare the context for that. `renderStyling` must be called afterwards to render any
* styling data that was set in this function (note that `updateClassProp` and
* `updateStyleProp` are designed to be run after this function is run).
*
* @param context The styling context that will be updated with the
* 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.
*/
export function updateClassMap(context, classesInput, directiveIndex) {
if (directiveIndex === void 0) { directiveIndex = 0; }
updateStylingMap(context, classesInput, true, directiveIndex);
}
/**
* Registers the provided multi style values to the context.
*
* This function will iterate over the provided `stylesInput` values and
* insert/update or remove them from the context at exactly the right spot.
*
* This function also takes in a directive which implies that the styling values will
* be evaluated for that directive with respect to any other styling that already exists
* on the context. When there are styles that conflict (e.g. say `ngStyle` and `[style]`
* both update the `width` property at the same time) then the styling algorithm code below
* will decide which one wins based on the directive styling prioritization mechanism. (This
* mechanism is better explained in render3/interfaces/styling.ts#directives).
*
* This function will not render any styling values on screen, but is rather designed to
* prepare the context for that. `renderStyling` must be called afterwards to render any
* styling data that was set in this function (note that `updateClassProp` and
* `updateStyleProp` are designed to be run after this function is run).
*
* @param context The styling context that will be updated with the
* newly provided style values.
* @param stylesInput The key/value map of CSS styles that will be used for the update.
*/
export function updateStyleMap(context, stylesInput, directiveIndex) {
if (directiveIndex === void 0) { directiveIndex = 0; }
updateStylingMap(context, stylesInput, false, directiveIndex);
}
function updateStylingMap(context, input, entryIsClassBased, directiveIndex) {
if (directiveIndex === void 0) { directiveIndex = 0; }
ngDevMode && (entryIsClassBased ? ngDevMode.classMap++ : ngDevMode.styleMap++);
ngDevMode && assertValidDirectiveIndex(context, directiveIndex);
// early exit (this is what's done to avoid using ctx.bind() to cache the value)
if (isMultiValueCacheHit(context, entryIsClassBased, directiveIndex, input))
return;
input =
input === NO_CHANGE ? readCachedMapValue(context, entryIsClassBased, directiveIndex) : input;
var element = context[0 /* ElementPosition */];
var playerBuilder = input instanceof BoundPlayerFactory ?
new ClassAndStylePlayerBuilder(input, element, entryIsClassBased ? 1 /* Class */ : 2 /* Style */) :
null;
var rawValue = playerBuilder ? input.value : input;
// the position is always the same, but whether the player builder gets set
// at all (depending if its set) will be reflected in the index value below...
var playerBuilderPosition = entryIsClassBased ? 1 /* ClassMapPlayerBuilderPosition */ :
3 /* StyleMapPlayerBuilderPosition */;
var playerBuilderIndex = playerBuilder ? playerBuilderPosition : 0;
var playerBuildersAreDirty = false;
if (hasPlayerBuilderChanged(context, playerBuilder, playerBuilderPosition)) {
setPlayerBuilder(context, playerBuilder, playerBuilderPosition);
playerBuildersAreDirty = true;
}
// each time a string-based value pops up then it shouldn't require a deep
// check of what's changed.
var startIndex;
var endIndex;
var propNames;
var applyAll = false;
if (entryIsClassBased) {
if (typeof rawValue == 'string') {
propNames = rawValue.split(/\s+/);
// this boolean is used to avoid having to create a key/value map of `true` values
// since a className string implies that all those classes are added
applyAll = true;
}
else {
propNames = rawValue ? Object.keys(rawValue) : EMPTY_ARRAY;
}
startIndex = getMultiClassesStartIndex(context);
endIndex = context.length;
}
else {
startIndex = getMultiStylesStartIndex(context);
endIndex = getMultiClassesStartIndex(context);
propNames = rawValue ? Object.keys(rawValue) : EMPTY_ARRAY;
}
var values = (rawValue || EMPTY_OBJ);
patchStylingMapIntoContext(context, directiveIndex, playerBuilderIndex, startIndex, endIndex, propNames, applyAll || values, input, entryIsClassBased);
if (playerBuildersAreDirty) {
setContextPlayersDirty(context, true);
}
ngDevMode && (entryIsClassBased ? ngDevMode.classMapCacheMiss++ : ngDevMode.styleMapCacheMiss++);
}
/**
* Applies the given multi styling (styles or classes) values to the context.
*
* The styling algorithm code that applies multi-level styling (things like `[style]` and `[class]`
* values) resides here.
*
* Because this function understands that multiple directives may all write to the `[style]` and
* `[class]` bindings (through host bindings), it relies of each directive applying its binding
* value in order. This means that a directive like `classADirective` will always fire before
* `classBDirective` and therefore its styling values (classes and styles) will always be evaluated
* in the same order. Because of this consistent ordering, the first directive has a higher priority
* than the second one. It is with this prioritzation mechanism that the styling algorithm knows how
* to merge and apply redudant styling properties.
*
* The function itself applies the key/value entries (or an array of keys) to
* the context in the following steps.
*
* STEP 1:
* First check to see what properties are already set and in use by another directive in the
* context (e.g. `ngClass` set the `width` value and `[style.width]="w"` in a directive is
* attempting to set it as well).
*
* STEP 2:
* All remaining properties (that were not set prior to this directive) are now updated in
* the context. Any new properties are inserted exactly at their spot in the context and any
* previously set properties are shifted to exactly where the cursor sits while iterating over
* the context. The end result is a balanced context that includes the exact ordering of the
* styling properties/values for the provided input from the directive.
*
* STEP 3:
* Any unmatched properties in the context that belong to the directive are set to null
*
* Once the updating phase is done, then the algorithm will decide whether or not to flag the
* follow-up directives (the directives that will pass in their styling values) depending on if
* the "shape" of the multi-value map has changed (either if any keys are removed or added or
* if there are any new `null` values). If any follow-up directives are flagged as dirty then the
* algorithm will run again for them. Otherwise if the shape did not change then any follow-up
* directives will not run (so long as their binding values stay the same).
*
* @returns the total amount of new slots that were allocated into the context due to new styling
* properties that were detected.
*/
function patchStylingMapIntoContext(context, directiveIndex, playerBuilderIndex, ctxStart, ctxEnd, props, values, cacheValue, entryIsClassBased) {
var dirty = false;
var cacheIndex = 1 /* ValuesStartPosition */ +
directiveIndex * 4 /* Size */;
// the cachedValues array is the registry of all multi style values (map values). Each
// value is stored (cached) each time is updated.
var cachedValues = context[entryIsClassBased ? 6 /* CachedMultiClasses */ : 7 /* CachedMultiStyles */];
// this is the index in which this directive has ownership access to write to this
// value (anything before is owned by a previous directive that is more important)
var ownershipValuesStartIndex = cachedValues[cacheIndex + 1 /* PositionStartOffset */];
var existingCachedValue = cachedValues[cacheIndex + 2 /* ValueOffset */];
var existingCachedValueCount = cachedValues[cacheIndex + 3 /* ValueCountOffset */];
var existingCachedValueIsDirty = cachedValues[cacheIndex + 0 /* DirtyFlagOffset */] === 1;
// A shape change means the provided map value has either removed or added new properties
// compared to what were in the last time. If a shape change occurs then it means that all
// follow-up multi-styling entries are obsolete and will be examined again when CD runs
// them. If a shape change has not occurred then there is no reason to check any other
// directive values if their identity has not changed. If a previous directive set this
// value as dirty (because its own shape changed) then this means that the object has been
// offset to a different area in the context. Because its value has been offset then it
// can't write to a region that it wrote to before (which may have been apart of another
// directive) and therefore its shape changes too.
var valuesEntryShapeChange = existingCachedValueIsDirty || ((!existingCachedValue && cacheValue) ? true : false);
var totalUniqueValues = 0;
var totalNewAllocatedSlots = 0;
// this is a trick to avoid building {key:value} map where all the values
// are `true` (this happens when a className string is provided instead of a
// map as an input value to this styling algorithm)
var applyAllProps = values === true;
// STEP 1:
// loop through the earlier directives and figure out if any properties here will be placed
// in their area (this happens when the value is null because the earlier directive erased it).
var ctxIndex = ctxStart;
var totalRemainingProperties = props.length;
while (ctxIndex < ownershipValuesStartIndex) {
var currentProp = getProp(context, ctxIndex);
if (totalRemainingProperties) {
for (var i = 0; i < props.length; i++) {
var mapProp = props[i];
var normalizedProp = mapProp ? (entryIsClassBased ? mapProp : hyphenate(mapProp)) : null;
if (normalizedProp && currentProp === normalizedProp) {
var currentValue = getValue(context, ctxIndex);
var currentDirectiveIndex = getDirectiveIndexFromEntry(context, ctxIndex);
var value = applyAllProps ? true : values[normalizedProp];
var currentFlag = getPointers(context, ctxIndex);
if (hasValueChanged(currentFlag, currentValue, value) &&
allowValueChange(currentValue, value, currentDirectiveIndex, directiveIndex)) {
setValue(context, ctxIndex, value);
setPlayerBuilderIndex(context, ctxIndex, playerBuilderIndex, directiveIndex);
if (hasInitialValueChanged(context, currentFlag, value)) {
setDirty(context, ctxIndex, true);
dirty = true;
}
}
props[i] = null;
totalRemainingProperties--;
break;
}
}
}
ctxIndex += 4 /* Size */;
}
// STEP 2:
// apply the left over properties to the context in the correct order.
if (totalRemainingProperties) {
var sanitizer = entryIsClassBased ? null : getStyleSanitizer(context, directiveIndex);
propertiesLoop: for (var i = 0; i < props.length; i++) {
var mapProp = props[i];
if (!mapProp) {
// this is an early exit in case a value was already encountered above in the
// previous loop (which means that the property was applied or rejected)
continue;
}
var value = applyAllProps ? true : values[mapProp];
var normalizedProp = entryIsClassBased ? mapProp : hyphenate(mapProp);
var isInsideOwnershipArea = ctxIndex >= ownershipValuesStartIndex;
for (var j = ctxIndex; j < ctxEnd; j += 4 /* Size */) {
var distantCtxProp = getProp(context, j);
if (distantCtxProp === normalizedProp) {
var distantCtxDirectiveIndex = getDirectiveIndexFromEntry(context, j);
var distantCtxPlayerBuilderIndex = getPlayerBuilderIndex(context, j);
var distantCtxValue = getValue(context, j);
var distantCtxFlag = getPointers(context, j);
if (allowValueChange(distantCtxValue, value, distantCtxDirectiveIndex, directiveIndex)) {
// even if the entry isn't updated (by value or directiveIndex) then
// it should still be moved over to the correct spot in the array so
// the iteration loop is tighter.
if (isInsideOwnershipArea) {
swapMultiContextEntries(context, ctxIndex, j);
totalUniqueValues++;
}
if (hasValueChanged(distantCtxFlag, distantCtxValue, value)) {
if (value === null || value === undefined && value !== distantCtxValue) {
valuesEntryShapeChange = true;
}
setValue(context, ctxIndex, value);
// SKIP IF INITIAL CHECK
// If the former `value` is `null` then it means that an initial value
// could be being rendered on screen. If that is the case then there is
// no point in updating the value in case it matches. In other words if the
// new value is the exact same as the previously rendered value (which
// happens to be the initial value) then do nothing.
if (distantCtxValue !== null ||
hasInitialValueChanged(context, distantCtxFlag, value)) {
setDirty(context, ctxIndex, true);
dirty = true;
}
}
if (distantCtxDirectiveIndex !== directiveIndex ||
playerBuilderIndex !== distantCtxPlayerBuilderIndex) {
setPlayerBuilderIndex(context, ctxIndex, playerBuilderIndex, directiveIndex);
}
}
ctxIndex += 4 /* Size */;
continue propertiesLoop;
}
}
// fallback case ... value not found at all in the context
if (value != null) {
valuesEntryShapeChange = true;
totalUniqueValues++;
var flag = prepareInitialFlag(context, normalizedProp, entryIsClassBased, sanitizer) |
1 /* Dirty */;
var insertionIndex = isInsideOwnershipArea ?
ctxIndex :
(ownershipValuesStartIndex + totalNewAllocatedSlots * 4 /* Size */);
insertNewMultiProperty(context, insertionIndex, entryIsClassBased, normalizedProp, flag, value, directiveIndex, playerBuilderIndex);
totalNewAllocatedSlots++;
ctxEnd += 4 /* Size */;
ctxIndex += 4 /* Size */;
dirty = true;
}
}
}
// STEP 3:
// Remove (nullify) any existing entries in the context that were not apart of the
// map input value that was passed into this algorithm for this directive.
while (ctxIndex < ctxEnd) {
valuesEntryShapeChange = true; // some values are missing
var ctxValue = getValue(context, ctxIndex);
var ctxFlag = getPointers(context, ctxIndex);
var ctxDirective = getDirectiveIndexFromEntry(context, ctxIndex);
if (ctxValue != null) {
valuesEntryShapeChange = true;
}
if (hasValueChanged(ctxFlag, ctxValue, null)) {
setValue(context, ctxIndex, null);
// only if the initial value is falsy then
if (hasInitialValueChanged(context, ctxFlag, ctxValue)) {
setDirty(context, ctxIndex, true);
dirty = true;
}
setPlayerBuilderIndex(context, ctxIndex, playerBuilderIndex, directiveIndex);
}
ctxIndex += 4 /* Size */;
}
// Because the object shape has changed, this means that all follow-up directives will need to
// reapply their values into the object. For this to happen, the cached array needs to be updated
// with dirty flags so that follow-up calls to `updateStylingMap` will reapply their styling code.
// the reapplication of styling code within the context will reshape it and update the offset
// values (also follow-up directives can write new values in case earlier directives set anything
// to null due to removals or falsy values).
valuesEntryShapeChange = valuesEntryShapeChange || existingCachedValueCount !== totalUniqueValues;
updateCachedMapValue(context, directiveIndex, entryIsClassBased, cacheValue, ownershipValuesStartIndex, ctxEnd, totalUniqueValues, valuesEntryShapeChange);
if (dirty) {
setContextDirty(context, true);
}
return totalNewAllocatedSlots;
}
/**
* Sets and resolves a single class value on the provided `StylingContext` so
* that they can be applied to the element once `renderStyling` is called.
*
* @param context The styling context that will be updated with the
* 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 forceOverride whether or not to skip all directive prioritization
* and just apply the value regardless.
*/
export function updateClassProp(context, offset, input, directiveIndex, forceOverride) {
if (directiveIndex === void 0) { directiveIndex = 0; }
updateSingleStylingValue(context, offset, input, true, directiveIndex, forceOverride);
}
/**
* Sets and resolves a single style value on the provided `StylingContext` so
* that they can be applied to the element once `renderStyling` is called.
*
* Note that prop-level styling values are considered higher priority than any styling that
* has been applied using `updateStylingMap`, therefore, when styling values are rendered
* then any styles/classes that have been applied using this function will be considered first
* (then multi values second and then initial values as a backup).
*
* @param context The styling context that will be updated with the
* 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 forceOverride whether or not to skip all directive prioritization
* and just apply the value regardless.
*/
export function updateStyleProp(context, offset, input, directiveIndex, forceOverride) {
if (directiveIndex === void 0) { directiveIndex = 0; }
updateSingleStylingValue(context, offset, input, false, directiveIndex, forceOverride);
}
function updateSingleStylingValue(context, offset, input, isClassBased, directiveIndex, forceOverride) {
ngDevMode && assertValidDirectiveIndex(context, directiveIndex);
var singleIndex = getSinglePropIndexValue(context, directiveIndex, offset, isClassBased);
var currValue = getValue(context, singleIndex);
var currFlag = getPointers(context, singleIndex);
var currDirective = getDirectiveIndexFromEntry(context, singleIndex);
var value = (input instanceof BoundPlayerFactory) ? input.value : input;
ngDevMode && ngDevMode.stylingProp++;
if (hasValueChanged(currFlag, currValue, value) &&
(forceOverride || allowValueChange(currValue, value, currDirective, directiveIndex))) {
var isClassBased_1 = (currFlag & 2 /* Class */) === 2 /* Class */;
var element = context[0 /* ElementPosition */];
var playerBuilder = input instanceof BoundPlayerFactory ?
new ClassAndStylePlayerBuilder(input, element, isClassBased_1 ? 1 /* Class */ : 2 /* Style */) :
null;
var value_1 = (playerBuilder ? input.value : input);
var currPlayerIndex = getPlayerBuilderIndex(context, singleIndex);
var playerBuildersAreDirty = false;
var playerBuilderIndex = playerBuilder ? currPlayerIndex : 0;
if (hasPlayerBuilderChanged(context, playerBuilder, currPlayerIndex)) {
var newIndex = setPlayerBuilder(context, playerBuilder, currPlayerIndex);
playerBuilderIndex = playerBuilder ? newIndex : 0;
playerBuildersAreDirty = true;
}
if (playerBuildersAreDirty || currDirective !== directiveIndex) {
setPlayerBuilderIndex(context, singleIndex, playerBuilderIndex, directiveIndex);
}
if (currDirective !== directiveIndex) {
var prop = getProp(context, singleIndex);
var sanitizer = getStyleSanitizer(context, directiveIndex);
setSanitizeFlag(context, singleIndex, (sanitizer && sanitizer(prop, null, 1 /* ValidateProperty */)) ? true : false);
}
// the value will always get updated (even if the dirty flag is skipped)
setValue(context, singleIndex, value_1);
var indexForMulti = getMultiOrSingleIndex(currFlag);
// if the value is the same in the multi-area then there's no point in re-assembling
var valueForMulti = getValue(context, indexForMulti);
if (!valueForMulti || hasValueChanged(currFlag, valueForMulti, value_1)) {
var multiDirty = false;
var singleDirty = true;
// only when the value is set to `null` should the multi-value get flagged
if (!valueExists(value_1, isClassBased_1) && valueExists(valueForMulti, isClassBased_1)) {
multiDirty = true;
singleDirty = false;
}
setDirty(context, indexForMulti, multiDirty);
setDirty(context, singleIndex, singleDirty);
setContextDirty(context, true);
}
if (playerBuildersAreDirty) {
setContextPlayersDirty(context, true);
}
ngDevMode && ngDevMode.stylingPropCacheMiss++;
}
}
/**
* Renders all queued styling using a renderer onto the given element.
*
* This function works by rendering any styles (that have been applied
* using `updateStylingMap`) and any classes (that have been applied using
* `updateStyleProp`) onto the provided element using the provided renderer.
* Just before the styles/classes are rendered a final key/value style map
* will be assembled (if `styleStore` or `classStore` are provided).
*
* @param lElement the element that the styles will be rendered on
* @param context The styling context that will be used to determine
* what styles will be rendered
* @param renderer the renderer that will be used to apply the styling
* @param classesStore if provided, the updated class values will be applied
* 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.
* @returns number the total amount of players that got queued for animation (if any)
*/
export function renderStyling(context, renderer, rootOrView, isFirstRender, classesStore, stylesStore, directiveIndex) {
if (directiveIndex === void 0) { directiveIndex = 0; }
var totalPlayersQueued = 0;
ngDevMode && ngDevMode.stylingApply++;
// 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);
if (isContextDirty(context)) {
ngDevMode && ngDevMode.stylingApplyCacheMiss++;
// 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
var native = context[0 /* ElementPosition */];
var flushPlayerBuilders = context[1 /* MasterFlagPosition */] & 8 /* PlayerBuildersDirty */;
var multiStartIndex = getMultiStylesStartIndex(context);
for (var i = 10 /* SingleStylesStartPosition */; i < context.length; i += 4 /* Size */) {
// there is no point in rendering styles that have not changed on screen
if (isDirty(context, i)) {
var flag = getPointers(context, i);
var directiveIndex_1 = getDirectiveIndexFromEntry(context, i);
var prop = getProp(context, i);
var value = getValue(context, i);
var styleSanitizer = (flag & 4 /* Sanitize */) ? getStyleSanitizer(context, directiveIndex_1) : null;
var playerBuilder = getPlayerBuilder(context, i);
var isClassBased = flag & 2 /* Class */ ? true : false;
var isInSingleRegion = i < multiStartIndex;
var valueToApply = value;
// 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
var multiIndex = getMultiOrSingleIndex(flag);
v