UNPKG

@angular/core

Version:

Angular - the core framework

975 lines • 275 kB
/** * @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