UNPKG

@angular/core

Version:

Angular - the core framework

866 lines • 264 kB
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