/** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import '../ng_dev_mode'; import {StyleSanitizeFn} from '../../sanitization/style_sanitizer'; import {getLContext} from '../context_discovery'; import {ACTIVE_INDEX, LContainer} from '../interfaces/container'; import {LContext} from '../interfaces/context'; import {PlayState, Player, PlayerContext, PlayerIndex} from '../interfaces/player'; import {RElement} from '../interfaces/renderer'; import {InitialStyles, StylingContext, StylingIndex} from '../interfaces/styling'; import {FLAGS, HEADER_OFFSET, HOST, LView, RootContext} from '../interfaces/view'; import {getTNode} from '../util'; import {CorePlayerHandler} from './core_player_handler'; const ANIMATION_PROP_PREFIX = '@'; export function createEmptyStylingContext( element?: RElement | null, sanitizer?: StyleSanitizeFn | null, initialStylingValues?: InitialStyles): StylingContext { return [ null, // PlayerContext sanitizer || null, // StyleSanitizer initialStylingValues || [null], // InitialStyles 0, // MasterFlags 0, // ClassOffset element || null, // Element null, // PreviousMultiClassValue null // PreviousMultiStyleValue ]; } /** * Used clone a copy of a pre-computed template of a styling context. * * A pre-computed template is designed to be computed once for a given element * (instructions.ts has logic for caching this). */ export function allocStylingContext( element: RElement | null, templateStyleContext: StylingContext): StylingContext { // each instance gets a copy const context = templateStyleContext.slice() as any as StylingContext; context[StylingIndex.ElementPosition] = element; return context; } /** * Retrieve the `StylingContext` at a given index. * * This method lazily creates the `StylingContext`. This is because in most cases * we have styling without any bindings. Creating `StylingContext` eagerly would mean that * every style declaration such as `
` would result `StyleContext` * which would create unnecessary memory pressure. * * @param index Index of the style allocation. See: `elementStyling`. * @param viewData The view to search for the styling context */ export function getStylingContext(index: number, viewData: LView): StylingContext { let storageIndex = index; let slotValue: LContainer|LView|StylingContext|RElement = viewData[storageIndex]; let wrapper: LContainer|LView|StylingContext = viewData; while (Array.isArray(slotValue)) { wrapper = slotValue; slotValue = slotValue[HOST] as LView | StylingContext | RElement; } if (isStylingContext(wrapper)) { return wrapper as StylingContext; } else { // This is an LView or an LContainer const stylingTemplate = getTNode(index - HEADER_OFFSET, viewData).stylingTemplate; if (wrapper !== viewData) { storageIndex = HOST; } return wrapper[storageIndex] = stylingTemplate ? allocStylingContext(slotValue, stylingTemplate) : createEmptyStylingContext(slotValue); } } export function isStylingContext(value: any): value is StylingContext { // Not an LView or an LContainer return Array.isArray(value) && typeof value[FLAGS] !== 'number' && typeof value[ACTIVE_INDEX] !== 'number'; } export function isAnimationProp(name: string): boolean { return name[0] === ANIMATION_PROP_PREFIX; } export function addPlayerInternal( playerContext: PlayerContext, rootContext: RootContext, element: HTMLElement, player: Player | null, playerContextIndex: number, ref?: any): boolean { ref = ref || element; if (playerContextIndex) { playerContext[playerContextIndex] = player; } else { playerContext.push(player); } if (player) { player.addEventListener(PlayState.Destroyed, () => { const index = playerContext.indexOf(player); const nonFactoryPlayerIndex = playerContext[PlayerIndex.NonBuilderPlayersStart]; // if the player is being removed from the factory side of the context // (which is where the [style] and [class] bindings do their thing) then // that side of the array cannot be resized since the respective bindings // have pointer index values that point to the associated factory instance if (index) { if (index < nonFactoryPlayerIndex) { playerContext[index] = null; } else { playerContext.splice(index, 1); } } player.destroy(); }); const playerHandler = rootContext.playerHandler || (rootContext.playerHandler = new CorePlayerHandler()); playerHandler.queuePlayer(player, ref); return true; } return false; } export function getPlayersInternal(playerContext: PlayerContext): Player[] { const players: Player[] = []; const nonFactoryPlayersStart = playerContext[PlayerIndex.NonBuilderPlayersStart]; // add all factory-based players (which are apart of [style] and [class] bindings) for (let i = PlayerIndex.PlayerBuildersStartPosition + PlayerIndex.PlayerOffsetPosition; i < nonFactoryPlayersStart; i += PlayerIndex.PlayerAndPlayerBuildersTupleSize) { const player = playerContext[i] as Player | null; if (player) { players.push(player); } } // add all custom players (not apart of [style] and [class] bindings) for (let i = nonFactoryPlayersStart; i < playerContext.length; i++) { players.push(playerContext[i] as Player); } return players; } export function getOrCreatePlayerContext(target: {}, context?: LContext | null): PlayerContext| null { context = context || getLContext(target) !; if (!context) { ngDevMode && throwInvalidRefError(); return null; } const {lView, nodeIndex} = context; const stylingContext = getStylingContext(nodeIndex, lView); return getPlayerContext(stylingContext) || allocPlayerContext(stylingContext); } export function getPlayerContext(stylingContext: StylingContext): PlayerContext|null { return stylingContext[StylingIndex.PlayerContext]; } export function allocPlayerContext(data: StylingContext): PlayerContext { return data[StylingIndex.PlayerContext] = [PlayerIndex.SinglePlayerBuildersStartPosition, null, null, null, null]; } export function throwInvalidRefError() { throw new Error('Only elements that exist in an Angular application can be used for animations'); }