@angular/core
Version:
Angular - the core framework
975 lines • 275 kB
JavaScript
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
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.
* @param {?} attrs
* @param {?} stylingStartIndex
* @param {?=} directiveIndex
* @return {?}
*/
export function initializeStaticContext(attrs, stylingStartIndex, directiveIndex = 0) {
/** @type {?} */
const 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
* @param {?} directiveIndex
* @return {?}
*/
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);
/** @type {?} */
let initialClasses = null;
/** @type {?} */
let initialStyles = null;
/** @type {?} */
let mode = -1;
for (let i = attrsStylingStartIndex; i < attrs.length; i++) {
/** @type {?} */
const attr = attrs[i];
if (typeof attr == 'number') {
mode = attr;
}
else if (mode == 1 /* Classes */) {
initialClasses = initialClasses || context[4 /* InitialClassValuesPosition */];
patchInitialStylingValue(initialClasses, (/** @type {?} */ (attr)), true, directiveIndex);
}
else if (mode == 2 /* Styles */) {
initialStyles = initialStyles || context[3 /* InitialStyleValuesPosition */];
patchInitialStylingValue(initialStyles, (/** @type {?} */ (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)
* @return {?}
*/
function patchInitialStylingValue(initialStyling, prop, value, directiveOwnerIndex) {
for (let i = 2 /* KeyValueStartPosition */; i < initialStyling.length; i += 3 /* Size */) {
/** @type {?} */
const key = initialStyling[i + 0 /* PropOffset */];
if (key === prop) {
/** @type {?} */
const existingValue = (/** @type {?} */ (initialStyling[i + 1 /* ValueOffset */]));
/** @type {?} */
const existingOwner = (/** @type {?} */ (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
* @param {?=} startIndex
* @return {?} the index that the classes were applied up until
*/
export function renderInitialClasses(element, context, renderer, startIndex) {
/** @type {?} */
const initialClasses = context[4 /* InitialClassValuesPosition */];
/** @type {?} */
let i = startIndex || 2 /* KeyValueStartPosition */;
while (i < initialClasses.length) {
/** @type {?} */
const value = initialClasses[i + 1 /* ValueOffset */];
if (value) {
setClass(element, (/** @type {?} */ (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
* @param {?=} startIndex
* @return {?} the index that the styles were applied up until
*/
export function renderInitialStyles(element, context, renderer, startIndex) {
/** @type {?} */
const initialStyles = context[3 /* InitialStyleValuesPosition */];
/** @type {?} */
let i = startIndex || 2 /* KeyValueStartPosition */;
while (i < initialStyles.length) {
/** @type {?} */
const value = initialStyles[i + 1 /* ValueOffset */];
if (value) {
setStyle(element, (/** @type {?} */ (initialStyles[i + 0 /* PropOffset */])), (/** @type {?} */ (value)), renderer, null);
}
i += 3 /* Size */;
}
return i;
}
/**
* @param {?} context
* @return {?}
*/
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 {?} directiveIndex
* @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.
* @return {?}
*/
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
/** @type {?} */
const 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.
/** @type {?} */
const singlePropOffsetValues = context[5 /* SinglePropOffsetPositions */];
/** @type {?} */
const totalCurrentClassBindings = singlePropOffsetValues[1 /* ClassesCountPosition */];
/** @type {?} */
const totalCurrentStyleBindings = singlePropOffsetValues[0 /* StylesCountPosition */];
/** @type {?} */
const cachedClassMapValues = context[6 /* CachedMultiClasses */];
/** @type {?} */
const cachedStyleMapValues = context[7 /* CachedMultiStyles */];
/** @type {?} */
const classesOffset = totalCurrentClassBindings * 4 /* Size */;
/** @type {?} */
const stylesOffset = totalCurrentStyleBindings * 4 /* Size */;
/** @type {?} */
const singleStylesStartIndex = 10 /* SingleStylesStartPosition */;
/** @type {?} */
let singleClassesStartIndex = singleStylesStartIndex + stylesOffset;
/** @type {?} */
let multiStylesStartIndex = singleClassesStartIndex + classesOffset;
/** @type {?} */
let 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.
/** @type {?} */
const 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
/** @type {?} */
let insertionOffset = 0;
/** @type {?} */
const filteredStyleBindingNames = [];
if (styleBindingNames && styleBindingNames.length) {
for (let i = 0; i < styleBindingNames.length; i++) {
/** @type {?} */
const name = styleBindingNames[i];
/** @type {?} */
let singlePropIndex = getMatchingBindingIndex(context, name, singleStylesStartIndex, singleClassesStartIndex);
if (singlePropIndex == -1) {
singlePropIndex = singleClassesStartIndex + insertionOffset;
insertionOffset += 4 /* Size */;
filteredStyleBindingNames.push(name);
}
singlePropOffsetValues.push(singlePropIndex);
}
}
// just like with the style binding loop above, the new class bindings get the same treatment...
/** @type {?} */
const filteredClassBindingNames = [];
if (classBindingNames && classBindingNames.length) {
for (let i = 0; i < classBindingNames.length; i++) {
/** @type {?} */
const name = classBindingNames[i];
/** @type {?} */
let singlePropIndex = getMatchingBindingIndex(context, name, singleClassesStartIndex, multiStylesStartIndex);
if (singlePropIndex == -1) {
singlePropIndex = multiStylesStartIndex + insertionOffset;
insertionOffset += 4 /* Size */;
filteredClassBindingNames.push(name);
}
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.
/** @type {?} */
let i = 2 /* ValueStartPosition */;
if (filteredStyleBindingNames.length) {
while (i < currentSinglePropsLength) {
/** @type {?} */
const totalStyles = singlePropOffsetValues[i + 0 /* StylesCountPosition */];
/** @type {?} */
const totalClasses = singlePropOffsetValues[i + 1 /* ClassesCountPosition */];
if (totalClasses) {
/** @type {?} */
const start = i + 2 /* ValueStartPosition */ + totalStyles;
for (let j = start; j < start + totalClasses; j++) {
singlePropOffsetValues[j] += filteredStyleBindingNames.length * 4 /* Size */;
}
}
/** @type {?} */
const total = totalStyles + totalClasses;
i += 2 /* ValueStartPosition */ + total;
}
}
/** @type {?} */
const 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 (let i = singleStylesStartIndex; i < context.length; i += 4 /* Size */) {
/** @type {?} */
const isMultiBased = i >= multiStylesStartIndex;
/** @type {?} */
const isClassBased = i >= (isMultiBased ? multiClassesStartIndex : singleClassesStartIndex);
/** @type {?} */
const flag = getPointers(context, i);
/** @type {?} */
const staticIndex = getInitialIndex(flag);
/** @type {?} */
let 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, pointers(flag, staticIndex, singleOrMultiIndex));
}
// this is where we make space in the context for the new style bindings
for (let i = 0; i < filteredStyleBindingNames.length * 4 /* Size */; i++) {
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 (let i = 0; i < filteredClassBindingNames.length * 4 /* Size */; i++) {
context.splice(multiStylesStartIndex, 0, null);
context.push(null);
multiStylesStartIndex++;
multiClassesStartIndex++;
}
/** @type {?} */
const initialClasses = context[4 /* InitialClassValuesPosition */];
/** @type {?} */
const 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 (let i = 0; i < totalNewEntries; i++) {
/** @type {?} */
const entryIsClassBased = i >= filteredStyleBindingNames.length;
/** @type {?} */
const adjustedIndex = entryIsClassBased ? (i - filteredStyleBindingNames.length) : i;
/** @type {?} */
const propName = entryIsClassBased ? filteredClassBindingNames[adjustedIndex] :
filteredStyleBindingNames[adjustedIndex];
/** @type {?} */
let multiIndex;
/** @type {?} */
let singleIndex;
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.
/** @type {?} */
let initialValuesToLookup = entryIsClassBased ? initialClasses : initialStyles;
/** @type {?} */
let indexForInitial = getInitialStylingValuesIndexOf(initialValuesToLookup, propName);
if (indexForInitial === -1) {
indexForInitial = addOrUpdateStaticStyle(null, initialValuesToLookup, propName, entryIsClassBased ? false : null, directiveIndex) +
1 /* ValueOffset */;
}
else {
indexForInitial += 1 /* ValueOffset */;
}
/** @type {?} */
const 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;
/** @type {?} */
const newStylesSpaceAllocationSize = filteredStyleBindingNames.length * 4 /* Size */;
/** @type {?} */
const newClassesSpaceAllocationSize = filteredClassBindingNames.length * 4 /* Size */;
// update the multi styles cache with a reference for the directive that was just inserted
/** @type {?} */
const directiveMultiStylesStartIndex = multiStylesStartIndex + totalCurrentStyleBindings * 4 /* Size */;
/** @type {?} */
const cachedStyleMapIndex = cachedStyleMapValues.length;
registerMultiMapEntry(context, directiveIndex, false, directiveMultiStylesStartIndex, filteredStyleBindingNames.length);
for (let i = 1 /* ValuesStartPosition */; i < cachedStyleMapIndex; i += 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 + 1 /* PositionStartOffset */] +=
newClassesSpaceAllocationSize + newStylesSpaceAllocationSize;
}
// update the multi classes cache with a reference for the directive that was just inserted
/** @type {?} */
const directiveMultiClassesStartIndex = multiClassesStartIndex + totalCurrentClassBindings * 4 /* Size */;
/** @type {?} */
const cachedClassMapIndex = cachedClassMapValues.length;
registerMultiMapEntry(context, directiveIndex, true, directiveMultiClassesStartIndex, filteredClassBindingNames.length);
for (let i = 1 /* ValuesStartPosition */; i < cachedClassMapIndex; i += 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 + 1 /* PositionStartOffset */] +=
(newStylesSpaceAllocationSize * 2) + newClassesSpaceAllocationSize;
}
// there is no initial value flag for the master index since it doesn't
// reference an initial style value
/** @type {?} */
const masterFlag = pointers(0, 0, multiStylesStartIndex);
setFlag(context, 1 /* MasterFlagPosition */, masterFlag);
}
/**
* Searches through the existing registry of directives
* @param {?} context
* @param {?} directiveIndex
* @param {?} staticModeOnly
* @param {?=} styleSanitizer
* @return {?}
*/
export function findOrPatchDirectiveIntoRegistry(context, directiveIndex, staticModeOnly, styleSanitizer) {
/** @type {?} */
const directiveRegistry = context[2 /* DirectiveRegistryPosition */];
/** @type {?} */
const index = directiveIndex * 2 /* Size */;
/** @type {?} */
const singlePropStartPosition = index + 0 /* SinglePropValuesIndexOffset */;
// this means that the directive has already been registered into the registry
if (index < directiveRegistry.length &&
((/** @type {?} */ (directiveRegistry[singlePropStartPosition]))) >= 0)
return false;
/** @type {?} */
const singlePropsStartIndex = staticModeOnly ? -1 : context[5 /* SinglePropOffsetPositions */].length;
allocateOrUpdateDirectiveIntoContext(context, directiveIndex, singlePropsStartIndex, styleSanitizer);
return true;
}
/**
* @param {?} context
* @param {?} bindingName
* @param {?} start
* @param {?} end
* @return {?}
*/
function getMatchingBindingIndex(context, bindingName, start, end) {
for (let 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 {?=} directiveIndex
* @return {?}
*/
export function updateClassMap(context, classesInput, 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.
* @param {?=} directiveIndex
* @return {?}
*/
export function updateStyleMap(context, stylesInput, directiveIndex = 0) {
updateStylingMap(context, stylesInput, false, directiveIndex);
}
/**
* @param {?} context
* @param {?} input
* @param {?} entryIsClassBased
* @param {?=} directiveIndex
* @return {?}
*/
function updateStylingMap(context, input, entryIsClassBased, 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;
/** @type {?} */
const element = (/** @type {?} */ ((/** @type {?} */ (context[0 /* ElementPosition */]))));
/** @type {?} */
const playerBuilder = input instanceof BoundPlayerFactory ?
new ClassAndStylePlayerBuilder((/** @type {?} */ (input)), element, entryIsClassBased ? 1 /* Class */ : 2 /* Style */) :
null;
/** @type {?} */
const rawValue = playerBuilder ? (/** @type {?} */ (((/** @type {?} */ (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...
/** @type {?} */
const playerBuilderPosition = entryIsClassBased ? 1 /* ClassMapPlayerBuilderPosition */ :
3 /* StyleMapPlayerBuilderPosition */;
/** @type {?} */
let playerBuilderIndex = playerBuilder ? playerBuilderPosition : 0;
/** @type {?} */
let 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.
/** @type {?} */
let startIndex;
/** @type {?} */
let endIndex;
/** @type {?} */
let propNames;
/** @type {?} */
let 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;
}
/** @type {?} */
const values = (/** @type {?} */ ((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).
*
* @param {?} context
* @param {?} directiveIndex
* @param {?} playerBuilderIndex
* @param {?} ctxStart
* @param {?} ctxEnd
* @param {?} props
* @param {?} values
* @param {?} cacheValue
* @param {?} entryIsClassBased
* @return {?} 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) {
/** @type {?} */
let dirty = false;
/** @type {?} */
const 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.
/** @type {?} */
const 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)
/** @type {?} */
const ownershipValuesStartIndex = cachedValues[cacheIndex + 1 /* PositionStartOffset */];
/** @type {?} */
const existingCachedValue = cachedValues[cacheIndex + 2 /* ValueOffset */];
/** @type {?} */
const existingCachedValueCount = cachedValues[cacheIndex + 3 /* ValueCountOffset */];
/** @type {?} */
const 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.
/** @type {?} */
let valuesEntryShapeChange = existingCachedValueIsDirty || ((!existingCachedValue && cacheValue) ? true : false);
/** @type {?} */
let totalUniqueValues = 0;
/** @type {?} */
let 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)
/** @type {?} */
const 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).
/** @type {?} */
let ctxIndex = ctxStart;
/** @type {?} */
let totalRemainingProperties = props.length;
while (ctxIndex < ownershipValuesStartIndex) {
/** @type {?} */
const currentProp = getProp(context, ctxIndex);
if (totalRemainingProperties) {
for (let i = 0; i < props.length; i++) {
/** @type {?} */
const mapProp = props[i];
/** @type {?} */
const normalizedProp = mapProp ? (entryIsClassBased ? mapProp : hyphenate(mapProp)) : null;
if (normalizedProp && currentProp === normalizedProp) {
/** @type {?} */
const currentValue = getValue(context, ctxIndex);
/** @type {?} */
const currentDirectiveIndex = getDirectiveIndexFromEntry(context, ctxIndex);
/** @type {?} */
const value = applyAllProps ? true : ((/** @type {?} */ (values)))[normalizedProp];
/** @type {?} */
const 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) {
/** @type {?} */
const sanitizer = entryIsClassBased ? null : getStyleSanitizer(context, directiveIndex);
propertiesLoop: for (let i = 0; i < props.length; i++) {
/** @type {?} */
const 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;
}
/** @type {?} */
const value = applyAllProps ? true : ((/** @type {?} */ (values)))[mapProp];
/** @type {?} */
const normalizedProp = entryIsClassBased ? mapProp : hyphenate(mapProp);
/** @type {?} */
const isInsideOwnershipArea = ctxIndex >= ownershipValuesStartIndex;
for (let j = ctxIndex; j < ctxEnd; j += 4 /* Size */) {
/** @type {?} */
const distantCtxProp = getProp(context, j);
if (distantCtxProp === normalizedProp) {
/** @type {?} */
const distantCtxDirectiveIndex = getDirectiveIndexFromEntry(context, j);
/** @type {?} */
const distantCtxPlayerBuilderIndex = getPlayerBuilderIndex(context, j);
/** @type {?} */
const distantCtxValue = getValue(context, j);
/** @type {?} */
const 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++;
/** @type {?} */
const flag = prepareInitialFlag(context, normalizedProp, entryIsClassBased, sanitizer) |
1 /* Dirty */;
/** @type {?} */
const 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
// some values are missing
/** @type {?} */
const ctxValue = getValue(context, ctxIndex);
/** @type {?} */
const ctxFlag = getPointers(context, ctxIndex);
/** @type {?} */
const 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 {?} input
* @param {?=} directiveIndex
* @param {?=} forceOverride whether or not to skip all directive prioritization
* and just apply the value regardless.
* @return {?}
*/
export function updateClassProp(context, offset, input, directiveIndex = 0, forceOverride) {
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 {?} input
* @param {?=} directiveIndex
* @param {?=} forceOverride whether or not to skip all directive prioritization
* and just apply the value regardless.
* @return {?}
*/
export function updateStyleProp(context, offset, input, directiveIndex = 0, forceOverride) {
updateSingleStylingValue(context, offset, input, false, directiveIndex, forceOverride);
}
/**
* @param {?} context
* @param {?} offset
* @param {?} input
* @param {?} isClassBased
* @param {?} directiveIndex
* @param {?=} forceOverride
* @return {?}
*/
function updateSingleStylingValue(context, offset, input, isClassBased, directiveIndex, forceOverride) {
ngDevMode && assertValidDirectiveIndex(context, directiveIndex);
/** @type {?} */
const singleIndex = getSinglePropIndexValue(context, directiveIndex, offset, isClassBased);
/** @type {?} */
const currValue = getValue(context, singleIndex);
/** @type {?} */
const currFlag = getPointers(context, singleIndex);
/** @type {?} */
const currDirective = getDirectiveIndexFromEntry(context, singleIndex);
/** @type {?} */
const value = (input instanceof BoundPlayerFactory) ? input.value : input;
ngDevMode && ngDevMode.stylingProp++;
if (hasValueChanged(currFlag, currValue, value) &&
(forceOverride || allowValueChange(currValue, value, currDirective, directiveIndex))) {
/** @type {?} */
const isClassBased = (currFlag & 2 /* Class */) === 2 /* Class */;
/** @type {?} */
const element = (/** @type {?} */ ((/** @type {?} */ (context[0 /* ElementPosition */]))));
/** @type {?} */
const playerBuilder = input instanceof BoundPlayerFactory ?
new ClassAndStylePlayerBuilder((/** @type {?} */ (input)), element, isClassBased ? 1 /* Class */ : 2 /* Style */) :
null;
/** @type {?} */
const value = (/** @type {?} */ ((playerBuilder ? ((/** @type {?} */ (input))).value : input)));
/** @type {?} */
const currPlayerIndex = getPlayerBuilderIndex(context, singleIndex);
/** @type {?} */
let playerBuildersAreDirty = false;
/** @type {?} */
let playerBuilderIndex = playerBuilder ? currPlayerIndex : 0;
if (hasPlayerBuilderChanged(context, playerBuilder, currPlayerIndex)) {
/** @type {?} */
const newIndex = setPlayerBuilder(context, playerBuilder, currPlayerIndex);
playerBuilderIndex = playerBuilder ? newIndex : 0;
playerBuildersAreDirty = true;
}
if (playerBuildersAreDirty || currDirective !== directiveIndex) {
setPlayerBuilderIndex(context, singleIndex, playerBuilderIndex, directiveIndex);
}
if (currDirective !== directiveIndex) {
/** @type {?} */
const prop = getProp(context, singleIndex);
/** @type {?} */
const 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);
/** @type {?} */
const indexForMulti = getMultiOrSingleIndex(currFlag);
// if the value is the same in the multi-area then there's no point in re-assembling
/** @type {?} */
const valueForMulti = getValue(context, indexForMulti);
if (!valueForMulti || hasValue