feat(ivy): enhance [style] and [class] bindings to be animation aware (#26096)
PR Close #26096
This commit is contained in:
		
							parent
							
								
									be337a2e52
								
							
						
					
					
						commit
						fa8e633be5
					
				| @ -163,6 +163,7 @@ export { | |||||||
| 
 | 
 | ||||||
| export { | export { | ||||||
|   Player as ɵPlayer, |   Player as ɵPlayer, | ||||||
|  |   PlayerFactory as ɵPlayerFactory, | ||||||
|   PlayState as ɵPlayState, |   PlayState as ɵPlayState, | ||||||
|   PlayerHandler as ɵPlayerHandler, |   PlayerHandler as ɵPlayerHandler, | ||||||
| } from './render3/interfaces/player'; | } from './render3/interfaces/player'; | ||||||
| @ -171,6 +172,10 @@ export { | |||||||
|   LContext as ɵLContext, |   LContext as ɵLContext, | ||||||
| } from './render3/interfaces/context'; | } from './render3/interfaces/context'; | ||||||
| 
 | 
 | ||||||
|  | export { | ||||||
|  |   bindPlayerFactory as ɵbindPlayerFactory, | ||||||
|  | } from './render3/styling/player_factory'; | ||||||
|  | 
 | ||||||
| export { | export { | ||||||
|   addPlayer as ɵaddPlayer, |   addPlayer as ɵaddPlayer, | ||||||
|   getPlayers as ɵgetPlayers, |   getPlayers as ɵgetPlayers, | ||||||
|  | |||||||
| @ -74,9 +74,9 @@ export function getHostComponent<T = {}>(target: {}): T|null { | |||||||
|  * Returns the `RootContext` instance that is associated with |  * Returns the `RootContext` instance that is associated with | ||||||
|  * the application where the target is situated. |  * the application where the target is situated. | ||||||
|  */ |  */ | ||||||
| export function getRootContext(target: {}): RootContext { | export function getRootContext(target: LViewData | {}): RootContext { | ||||||
|   const context = loadContext(target) !; |   const lViewData = Array.isArray(target) ? target : loadContext(target) !.lViewData; | ||||||
|   const rootLViewData = getRootView(context.lViewData); |   const rootLViewData = getRootView(lViewData); | ||||||
|   return rootLViewData[CONTEXT] as RootContext; |   return rootLViewData[CONTEXT] as RootContext; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -20,6 +20,7 @@ import {ACTIVE_INDEX, LContainer, VIEWS} from './interfaces/container'; | |||||||
| import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; | import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; | ||||||
| import {INJECTOR_SIZE} from './interfaces/injector'; | import {INJECTOR_SIZE} from './interfaces/injector'; | ||||||
| import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TProjectionNode, TViewNode} from './interfaces/node'; | import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TProjectionNode, TViewNode} from './interfaces/node'; | ||||||
|  | import {PlayerFactory} from './interfaces/player'; | ||||||
| import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection'; | import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection'; | ||||||
| import {LQueries} from './interfaces/query'; | import {LQueries} from './interfaces/query'; | ||||||
| import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, RendererFactory3, isProceduralRenderer} from './interfaces/renderer'; | import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, RendererFactory3, isProceduralRenderer} from './interfaces/renderer'; | ||||||
| @ -27,9 +28,10 @@ import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, Curre | |||||||
| import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; | import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; | ||||||
| import {appendChild, appendProjectedNode, createTextNode, findComponentView, getLViewChild, getRenderParent, insertView, removeView} from './node_manipulation'; | import {appendChild, appendProjectedNode, createTextNode, findComponentView, getLViewChild, getRenderParent, insertView, removeView} from './node_manipulation'; | ||||||
| import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher'; | import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher'; | ||||||
| import {createStylingContextTemplate, renderStyling as renderElementStyles, updateClassProp as updateElementClassProp, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings'; | import {createStylingContextTemplate, renderStyleAndClassBindings, updateClassProp as updateElementClassProp, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings'; | ||||||
|  | import {BoundPlayerFactory} from './styling/player_factory'; | ||||||
| import {getStylingContext} from './styling/util'; | import {getStylingContext} from './styling/util'; | ||||||
| import {assertDataInRangeInternal, getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootView, getTNode, isComponent, isContentQueryHost, isDifferent, loadInternal, readPatchedLViewData, stringify} from './util'; | import {assertDataInRangeInternal, getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isContentQueryHost, isDifferent, loadInternal, readPatchedLViewData, stringify} from './util'; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -1501,9 +1503,11 @@ function generatePropertyAliases( | |||||||
|  *        renaming as part of minification. |  *        renaming as part of minification. | ||||||
|  * @param value A value indicating if a given class should be added or removed. |  * @param value A value indicating if a given class should be added or removed. | ||||||
|  */ |  */ | ||||||
| export function elementClassProp<T>( | export function elementClassProp( | ||||||
|     index: number, stylingIndex: number, value: T | NO_CHANGE): void { |     index: number, stylingIndex: number, value: boolean | PlayerFactory): void { | ||||||
|   updateElementClassProp(getStylingContext(index, viewData), stylingIndex, value ? true : false); |   const val = | ||||||
|  |       (value instanceof BoundPlayerFactory) ? (value as BoundPlayerFactory<boolean>) : (!!value); | ||||||
|  |   updateElementClassProp(getStylingContext(index, viewData), stylingIndex, val); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -1534,7 +1538,7 @@ export function elementClassProp<T>( | |||||||
|  * @param styleSanitizer An optional sanitizer function that will be used (if provided) |  * @param styleSanitizer An optional sanitizer function that will be used (if provided) | ||||||
|  *   to sanitize the any CSS property values that are applied to the element (during rendering). |  *   to sanitize the any CSS property values that are applied to the element (during rendering). | ||||||
|  */ |  */ | ||||||
| export function elementStyling<T>( | export function elementStyling( | ||||||
|     classDeclarations?: (string | boolean | InitialStylingFlags)[] | null, |     classDeclarations?: (string | boolean | InitialStylingFlags)[] | null, | ||||||
|     styleDeclarations?: (string | boolean | InitialStylingFlags)[] | null, |     styleDeclarations?: (string | boolean | InitialStylingFlags)[] | null, | ||||||
|     styleSanitizer?: StyleSanitizeFn | null): void { |     styleSanitizer?: StyleSanitizeFn | null): void { | ||||||
| @ -1565,8 +1569,13 @@ export function elementStyling<T>( | |||||||
|  *        specifically for element styling--the index must be the next index after the element |  *        specifically for element styling--the index must be the next index after the element | ||||||
|  *        index.) |  *        index.) | ||||||
|  */ |  */ | ||||||
| export function elementStylingApply<T>(index: number): void { | export function elementStylingApply(index: number): void { | ||||||
|   renderElementStyles(getStylingContext(index, viewData), renderer); |   const totalPlayersQueued = | ||||||
|  |       renderStyleAndClassBindings(getStylingContext(index, viewData), renderer, viewData); | ||||||
|  |   if (totalPlayersQueued > 0) { | ||||||
|  |     const rootContext = getRootContext(viewData); | ||||||
|  |     scheduleTick(rootContext, RootContextFlags.FlushPlayers); | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -1589,8 +1598,9 @@ export function elementStylingApply<T>(index: number): void { | |||||||
|  *        Note that when a suffix is provided then the underlying sanitizer will |  *        Note that when a suffix is provided then the underlying sanitizer will | ||||||
|  *        be ignored. |  *        be ignored. | ||||||
|  */ |  */ | ||||||
| export function elementStyleProp<T>( | export function elementStyleProp( | ||||||
|     index: number, styleIndex: number, value: T | null, suffix?: string): void { |     index: number, styleIndex: number, value: string | number | String | PlayerFactory | null, | ||||||
|  |     suffix?: string): void { | ||||||
|   let valueToAdd: string|null = null; |   let valueToAdd: string|null = null; | ||||||
|   if (value) { |   if (value) { | ||||||
|     if (suffix) { |     if (suffix) { | ||||||
| @ -2386,11 +2396,7 @@ export function markViewDirty(view: LViewData): void { | |||||||
|   ngDevMode && assertDefined(currentView[CONTEXT], 'rootContext should be defined'); |   ngDevMode && assertDefined(currentView[CONTEXT], 'rootContext should be defined'); | ||||||
| 
 | 
 | ||||||
|   const rootContext = currentView[CONTEXT] as RootContext; |   const rootContext = currentView[CONTEXT] as RootContext; | ||||||
|   const nothingScheduled = rootContext.flags === RootContextFlags.Empty; |   scheduleTick(rootContext, RootContextFlags.DetectChanges); | ||||||
|   rootContext.flags |= RootContextFlags.DetectChanges; |  | ||||||
|   if (nothingScheduled) { |  | ||||||
|     scheduleTick(rootContext); |  | ||||||
|   } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -2404,8 +2410,11 @@ export function markViewDirty(view: LViewData): void { | |||||||
|  * `scheduleTick` requests. The scheduling function can be overridden in |  * `scheduleTick` requests. The scheduling function can be overridden in | ||||||
|  * `renderComponent`'s `scheduler` option. |  * `renderComponent`'s `scheduler` option. | ||||||
|  */ |  */ | ||||||
| export function scheduleTick<T>(rootContext: RootContext) { | export function scheduleTick<T>(rootContext: RootContext, flags: RootContextFlags) { | ||||||
|   if (rootContext.clean == _CLEAN_PROMISE) { |   const nothingScheduled = rootContext.flags === RootContextFlags.Empty; | ||||||
|  |   rootContext.flags |= flags; | ||||||
|  | 
 | ||||||
|  |   if (nothingScheduled && rootContext.clean == _CLEAN_PROMISE) { | ||||||
|     let res: null|((val: null) => void); |     let res: null|((val: null) => void); | ||||||
|     rootContext.clean = new Promise<null>((r) => res = r); |     rootContext.clean = new Promise<null>((r) => res = r); | ||||||
|     rootContext.scheduler(() => { |     rootContext.scheduler(() => { | ||||||
|  | |||||||
| @ -19,6 +19,43 @@ export interface Player { | |||||||
|   addEventListener(state: PlayState|string, cb: (data?: any) => any): void; |   addEventListener(state: PlayState|string, cb: (data?: any) => any): void; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export const enum BindingType { | ||||||
|  |   Unset = 0, | ||||||
|  |   Class = 2, | ||||||
|  |   Style = 3, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface BindingStore { setValue(prop: string, value: any): void; } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Defines the shape which produces the Player. | ||||||
|  |  * | ||||||
|  |  * Used to produce a player that will be placed on an element that contains | ||||||
|  |  * styling bindings that make use of the player. This function is designed | ||||||
|  |  * to be used with `PlayerFactory`. | ||||||
|  |  */ | ||||||
|  | export interface PlayerFactoryBuildFn { | ||||||
|  |   (element: HTMLElement, type: BindingType, values: {[key: string]: any}, | ||||||
|  |    currentPlayer: Player|null): Player|null; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Used as a reference to build a player from a styling template binding | ||||||
|  |  * (`[style]` and `[class]`). | ||||||
|  |  * | ||||||
|  |  * The `fn` function will be called once any styling-related changes are | ||||||
|  |  * evaluated on an element and is expected to return a player that will | ||||||
|  |  * be then run on the element. | ||||||
|  |  * | ||||||
|  |  * `[style]`, `[style.prop]`, `[class]` and `[class.name]` template bindings | ||||||
|  |  * all accept a `PlayerFactory` as input and this player factories. | ||||||
|  |  */ | ||||||
|  | export interface PlayerFactory { '__brand__': 'Brand for PlayerFactory that nothing will match'; } | ||||||
|  | 
 | ||||||
|  | export interface PlayerBuilder extends BindingStore { | ||||||
|  |   buildPlayer(currentPlayer: Player|null): Player|undefined|null; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * The state of a given player |  * The state of a given player | ||||||
|  * |  * | ||||||
| @ -29,11 +66,15 @@ export interface Player { | |||||||
| export const enum PlayState {Pending = 0, Running = 1, Paused = 2, Finished = 100, Destroyed = 200} | export const enum PlayState {Pending = 0, Running = 1, Paused = 2, Finished = 100, Destroyed = 200} | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * The context that stores all active animation players present on an element. |  * The context that stores all the active players and queued player factories present on an element. | ||||||
|  */ |  */ | ||||||
| export declare type PlayerContext = Player[]; | export interface PlayerContext extends Array<null|number|Player|PlayerBuilder> { | ||||||
| export declare type ComponentInstance = {}; |   [PlayerIndex.NonBuilderPlayersStart]: number; | ||||||
| export declare type DirectiveInstance = {}; |   [PlayerIndex.ClassMapPlayerBuilderPosition]: PlayerBuilder|null; | ||||||
|  |   [PlayerIndex.ClassMapPlayerPosition]: Player|null; | ||||||
|  |   [PlayerIndex.StyleMapPlayerBuilderPosition]: PlayerBuilder|null; | ||||||
|  |   [PlayerIndex.StyleMapPlayerPosition]: Player|null; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Designed to be used as an injection service to capture all animation players. |  * Designed to be used as an injection service to capture all animation players. | ||||||
| @ -54,3 +95,29 @@ export interface PlayerHandler { | |||||||
|    */ |    */ | ||||||
|   queuePlayer(player: Player, context: ComponentInstance|DirectiveInstance|HTMLElement): void; |   queuePlayer(player: Player, context: ComponentInstance|DirectiveInstance|HTMLElement): void; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export const enum PlayerIndex { | ||||||
|  |   // The position where the index that reveals where players start in the PlayerContext
 | ||||||
|  |   NonBuilderPlayersStart = 0, | ||||||
|  |   // The position where the player builder lives (which handles {key:value} map expression) for
 | ||||||
|  |   // classes
 | ||||||
|  |   ClassMapPlayerBuilderPosition = 1, | ||||||
|  |   // The position where the last player assigned to the class player builder is stored
 | ||||||
|  |   ClassMapPlayerPosition = 2, | ||||||
|  |   // The position where the player builder lives (which handles {key:value} map expression) for
 | ||||||
|  |   // styles
 | ||||||
|  |   StyleMapPlayerBuilderPosition = 3, | ||||||
|  |   // The position where the last player assigned to the style player builder is stored
 | ||||||
|  |   StyleMapPlayerPosition = 4, | ||||||
|  |   // The position where any player builders start in the PlayerContext
 | ||||||
|  |   PlayerBuildersStartPosition = 1, | ||||||
|  |   // The position where non map-based player builders start in the PlayerContext
 | ||||||
|  |   SinglePlayerBuildersStartPosition = 5, | ||||||
|  |   // For each player builder there is a player in the player context (therefore size = 2)
 | ||||||
|  |   PlayerAndPlayerBuildersTupleSize = 2, | ||||||
|  |   // The player exists next to the player builder in the list
 | ||||||
|  |   PlayerOffsetPosition = 1, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export declare type ComponentInstance = {}; | ||||||
|  | export declare type DirectiveInstance = {}; | ||||||
|  | |||||||
| @ -5,14 +5,11 @@ | |||||||
|  * Use of this source code is governed by an MIT-style license that can be |  * 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
 |  * found in the LICENSE file at https://angular.io/license
 | ||||||
|  */ |  */ | ||||||
| 
 |  | ||||||
| import {StyleSanitizeFn} from '../../sanitization/style_sanitizer'; | import {StyleSanitizeFn} from '../../sanitization/style_sanitizer'; | ||||||
| import {RElement} from '../interfaces/renderer'; | import {RElement} from '../interfaces/renderer'; | ||||||
| 
 |  | ||||||
| import {PlayerContext} from './player'; | import {PlayerContext} from './player'; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * The styling context acts as a styling manifest (shaped as an array) for determining which |  * The styling context acts as a styling manifest (shaped as an array) for determining which | ||||||
|  * styling properties have been assigned via the provided `updateStylingMap`, `updateStyleProp` |  * styling properties have been assigned via the provided `updateStylingMap`, `updateStyleProp` | ||||||
| @ -184,17 +181,19 @@ export interface InitialStyles extends Array<string|null|boolean> { [0]: null; } | |||||||
|  */ |  */ | ||||||
| export const enum StylingFlags { | export const enum StylingFlags { | ||||||
|   // Implies no configurations
 |   // Implies no configurations
 | ||||||
|   None = 0b000, |   None = 0b0000, | ||||||
|   // Whether or not the entry or context itself is dirty
 |   // Whether or not the entry or context itself is dirty
 | ||||||
|   Dirty = 0b001, |   Dirty = 0b0001, | ||||||
|   // Whether or not this is a class-based assignment
 |   // Whether or not this is a class-based assignment
 | ||||||
|   Class = 0b010, |   Class = 0b0010, | ||||||
|   // Whether or not a sanitizer was applied to this property
 |   // Whether or not a sanitizer was applied to this property
 | ||||||
|   Sanitize = 0b100, |   Sanitize = 0b0100, | ||||||
|  |   // Whether or not any player builders within need to produce new players
 | ||||||
|  |   PlayerBuildersDirty = 0b1000, | ||||||
|   // The max amount of bits used to represent these configuration values
 |   // The max amount of bits used to represent these configuration values
 | ||||||
|   BitCountSize = 3, |   BitCountSize = 4, | ||||||
|   // There are only three bits here
 |   // There are only three bits here
 | ||||||
|   BitMask = 0b111 |   BitMask = 0b1111 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** Used as numeric pointer values to determine what cells to update in the `StylingContext` */ | /** Used as numeric pointer values to determine what cells to update in the `StylingContext` */ | ||||||
| @ -222,10 +221,11 @@ export const enum StylingIndex { | |||||||
|   FlagsOffset = 0, |   FlagsOffset = 0, | ||||||
|   PropertyOffset = 1, |   PropertyOffset = 1, | ||||||
|   ValueOffset = 2, |   ValueOffset = 2, | ||||||
|   // Size of each multi or single entry (flag + prop + value)
 |   PlayerBuilderIndexOffset = 3, | ||||||
|   Size = 3, |   // Size of each multi or single entry (flag + prop + value + playerBuilderIndex)
 | ||||||
|  |   Size = 4, | ||||||
|   // Each flag has a binary digit length of this value
 |   // Each flag has a binary digit length of this value
 | ||||||
|   BitCountSize = 14,  // (32 - 3) / 2 = ~14
 |   BitCountSize = 14,  // (32 - 4) / 2 = ~14
 | ||||||
|   // The binary digit value as a mask
 |   // The binary digit value as a mask
 | ||||||
|   BitMask = 0b11111111111111  // 14 bits
 |   BitMask = 0b11111111111111,  // 14 bits
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -9,15 +9,16 @@ | |||||||
| import {Injector} from '../../di/injector'; | import {Injector} from '../../di/injector'; | ||||||
| import {QueryList} from '../../linker'; | import {QueryList} from '../../linker'; | ||||||
| import {Sanitizer} from '../../sanitization/security'; | import {Sanitizer} from '../../sanitization/security'; | ||||||
| import {PlayerHandler} from '../interfaces/player'; |  | ||||||
| 
 | 
 | ||||||
| import {LContainer} from './container'; | import {LContainer} from './container'; | ||||||
| import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefList, HostBindingsFunction, PipeDef, PipeDefList} from './definition'; | import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefList, HostBindingsFunction, PipeDef, PipeDefList} from './definition'; | ||||||
| import {TElementNode, TNode, TViewNode} from './node'; | import {TElementNode, TNode, TViewNode} from './node'; | ||||||
|  | import {PlayerHandler} from './player'; | ||||||
| import {LQueries} from './query'; | import {LQueries} from './query'; | ||||||
| import {RElement, Renderer3} from './renderer'; | import {RElement, Renderer3} from './renderer'; | ||||||
| import {StylingContext} from './styling'; | import {StylingContext} from './styling'; | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| /** Size of LViewData's header. Necessary to adjust for it when setting slots.  */ | /** Size of LViewData's header. Necessary to adjust for it when setting slots.  */ | ||||||
| export const HEADER_OFFSET = 17; | export const HEADER_OFFSET = 17; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -5,43 +5,60 @@ | |||||||
|  * Use of this source code is governed by an MIT-style license that can be |  * 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
 |  * found in the LICENSE file at https://angular.io/license
 | ||||||
|  */ |  */ | ||||||
| import {getContext} from './context_discovery'; | import './ng_dev_mode'; | ||||||
| import {scheduleTick} from './instructions'; |  | ||||||
| import {ComponentInstance, DirectiveInstance, PlayState, Player} from './interfaces/player'; |  | ||||||
| import {RootContextFlags} from './interfaces/view'; |  | ||||||
| import {CorePlayerHandler} from './styling/core_player_handler'; |  | ||||||
| import {getOrCreatePlayerContext} from './styling/util'; |  | ||||||
| import {getRootContext} from './util'; |  | ||||||
| 
 | 
 | ||||||
|  | import {getContext} from './context_discovery'; | ||||||
|  | import {getRootContext} from './discovery_utils'; | ||||||
|  | import {scheduleTick} from './instructions'; | ||||||
|  | import {ComponentInstance, DirectiveInstance, Player} from './interfaces/player'; | ||||||
|  | import {HEADER_OFFSET, RootContextFlags} from './interfaces/view'; | ||||||
|  | import {addPlayerInternal, getOrCreatePlayerContext, getPlayerContext, getPlayersInternal, getStylingContext, throwInvalidRefError} from './styling/util'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Adds a player to an element, directive or component instance that will later be | ||||||
|  |  * animated once change detection has passed. | ||||||
|  |  * | ||||||
|  |  * When a player is added to a reference it will stay active until `player.destroy()` | ||||||
|  |  * is called. Once called then the player will be removed from the active players | ||||||
|  |  * present on the associated ref instance. | ||||||
|  |  * | ||||||
|  |  * To get a list of all the active players on an element see [getPlayers]. | ||||||
|  |  * | ||||||
|  |  * @param ref The element, directive or component that the player will be placed on. | ||||||
|  |  * @param player The player that will be triggered to play once change detection has run. | ||||||
|  |  */ | ||||||
| export function addPlayer( | export function addPlayer( | ||||||
|     ref: ComponentInstance | DirectiveInstance | HTMLElement, player: Player): void { |     ref: ComponentInstance | DirectiveInstance | HTMLElement, player: Player): void { | ||||||
|   const elementContext = getContext(ref) !; |   const context = getContext(ref); | ||||||
|   const animationContext = getOrCreatePlayerContext(elementContext.native, elementContext) !; |   if (!context) { | ||||||
|   animationContext.push(player); |     ngDevMode && throwInvalidRefError(); | ||||||
| 
 |     return; | ||||||
|   player.addEventListener(PlayState.Destroyed, () => { |  | ||||||
|     const index = animationContext.indexOf(player); |  | ||||||
|     if (index >= 0) { |  | ||||||
|       animationContext.splice(index, 1); |  | ||||||
|     } |  | ||||||
|     player.destroy(); |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   const rootContext = getRootContext(elementContext.lViewData); |  | ||||||
|   const playerHandler = |  | ||||||
|       rootContext.playerHandler || (rootContext.playerHandler = new CorePlayerHandler()); |  | ||||||
|   playerHandler.queuePlayer(player, ref); |  | ||||||
| 
 |  | ||||||
|   const nothingScheduled = rootContext.flags === RootContextFlags.Empty; |  | ||||||
| 
 |  | ||||||
|   // change detection may or may not happen therefore
 |  | ||||||
|   // the core code needs to be kicked off to flush the animations
 |  | ||||||
|   rootContext.flags |= RootContextFlags.FlushPlayers; |  | ||||||
|   if (nothingScheduled) { |  | ||||||
|     scheduleTick(rootContext); |  | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   const element = context.native as HTMLElement; | ||||||
|  |   const lViewData = context.lViewData; | ||||||
|  |   const playerContext = getOrCreatePlayerContext(element, context) !; | ||||||
|  |   const rootContext = getRootContext(lViewData); | ||||||
|  |   addPlayerInternal(playerContext, rootContext, element, player, 0, ref); | ||||||
|  |   scheduleTick(rootContext, RootContextFlags.FlushPlayers); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * Returns a list of all the active players present on the provided ref instance (which can | ||||||
|  |  * be an instance of a directive, component or element). | ||||||
|  |  * | ||||||
|  |  * This function will only return players that have been added to the ref instance using | ||||||
|  |  * `addPlayer` or any players that are active through any template styling bindings | ||||||
|  |  * (`[style]`, `[style.prop]`, `[class]` and `[class.name]`). | ||||||
|  |  */ | ||||||
| export function getPlayers(ref: ComponentInstance | DirectiveInstance | HTMLElement): Player[] { | export function getPlayers(ref: ComponentInstance | DirectiveInstance | HTMLElement): Player[] { | ||||||
|   return getOrCreatePlayerContext(ref); |   const context = getContext(ref); | ||||||
|  |   if (!context) { | ||||||
|  |     ngDevMode && throwInvalidRefError(); | ||||||
|  |     return []; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const stylingContext = getStylingContext(context.nodeIndex - HEADER_OFFSET, context.lViewData); | ||||||
|  |   const playerContext = stylingContext ? getPlayerContext(stylingContext) : null; | ||||||
|  |   return playerContext ? getPlayersInternal(playerContext) : []; | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,13 +5,20 @@ | |||||||
|  * Use of this source code is governed by an MIT-style license that can be |  * 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
 |  * found in the LICENSE file at https://angular.io/license
 | ||||||
|  */ |  */ | ||||||
| 
 |  | ||||||
| import {StyleSanitizeFn} from '../../sanitization/style_sanitizer'; | import {StyleSanitizeFn} from '../../sanitization/style_sanitizer'; | ||||||
| import {InitialStylingFlags} from '../interfaces/definition'; | import {InitialStylingFlags} from '../interfaces/definition'; | ||||||
|  | import {BindingStore, BindingType, Player, PlayerBuilder, PlayerFactory, PlayerIndex} from '../interfaces/player'; | ||||||
| import {Renderer3, RendererStyleFlags3, isProceduralRenderer} from '../interfaces/renderer'; | import {Renderer3, RendererStyleFlags3, isProceduralRenderer} from '../interfaces/renderer'; | ||||||
| import {InitialStyles, StylingContext, StylingFlags, StylingIndex} from '../interfaces/styling'; | import {InitialStyles, StylingContext, StylingFlags, StylingIndex} from '../interfaces/styling'; | ||||||
|  | import {LViewData, RootContext} from '../interfaces/view'; | ||||||
|  | import {getRootContext} from '../util'; | ||||||
|  | 
 | ||||||
|  | import {BoundPlayerFactory} from './player_factory'; | ||||||
|  | import {addPlayerInternal, allocPlayerContext, createEmptyStylingContext, getPlayerContext} from './util'; | ||||||
|  | 
 | ||||||
|  | const EMPTY_ARR: any[] = []; | ||||||
|  | const EMPTY_OBJ: {[key: string]: any} = {}; | ||||||
| 
 | 
 | ||||||
| import {EMPTY_ARR, EMPTY_OBJ, createEmptyStylingContext} from './util'; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Creates a styling context template where styling information is stored. |  * Creates a styling context template where styling information is stored. | ||||||
| @ -124,12 +131,14 @@ export function createStylingContextTemplate( | |||||||
|     setFlag(context, indexForSingle, pointers(initialFlag, indexForInitial, indexForMulti)); |     setFlag(context, indexForSingle, pointers(initialFlag, indexForInitial, indexForMulti)); | ||||||
|     setProp(context, indexForSingle, prop); |     setProp(context, indexForSingle, prop); | ||||||
|     setValue(context, indexForSingle, null); |     setValue(context, indexForSingle, null); | ||||||
|  |     setPlayerBuilderIndex(context, indexForSingle, 0); | ||||||
| 
 | 
 | ||||||
|     const flagForMulti = |     const flagForMulti = | ||||||
|         initialFlag | (initialValue !== null ? StylingFlags.Dirty : StylingFlags.None); |         initialFlag | (initialValue !== null ? StylingFlags.Dirty : StylingFlags.None); | ||||||
|     setFlag(context, indexForMulti, pointers(flagForMulti, indexForInitial, indexForSingle)); |     setFlag(context, indexForMulti, pointers(flagForMulti, indexForInitial, indexForSingle)); | ||||||
|     setProp(context, indexForMulti, prop); |     setProp(context, indexForMulti, prop); | ||||||
|     setValue(context, indexForMulti, null); |     setValue(context, indexForMulti, null); | ||||||
|  |     setPlayerBuilderIndex(context, indexForMulti, 0); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // there is no initial value flag for the master index since it doesn't
 |   // there is no initial value flag for the master index since it doesn't
 | ||||||
| @ -142,7 +151,7 @@ export function createStylingContextTemplate( | |||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Sets and resolves all `multi` styling on an `StylingContext` so that they can be |  * Sets and resolves all `multi` styling on an `StylingContext` so that they can be | ||||||
|  * applied to the element once `renderStyling` is called. |  * applied to the element once `renderStyleAndClassBindings` is called. | ||||||
|  * |  * | ||||||
|  * All missing styles/class (any values that are not provided in the new `styles` |  * All missing styles/class (any values that are not provided in the new `styles` | ||||||
|  * or `classes` params) will resolve to `null` within their respective positions |  * or `classes` params) will resolve to `null` within their respective positions | ||||||
| @ -150,43 +159,73 @@ export function createStylingContextTemplate( | |||||||
|  * |  * | ||||||
|  * @param context The styling context that will be updated with the |  * @param context The styling context that will be updated with the | ||||||
|  *    newly provided style values. |  *    newly provided style values. | ||||||
|  * @param classes The key/value map of CSS class names that will be used for the update. |  * @param classesInput The key/value map of CSS class names that will be used for the update. | ||||||
|  * @param styles The key/value map of CSS styles 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 updateStylingMap( | export function updateStylingMap( | ||||||
|     context: StylingContext, classes: {[key: string]: any} | string | null, |     context: StylingContext, classesInput: {[key: string]: any} | string | | ||||||
|     styles?: {[key: string]: any} | null): void { |         BoundPlayerFactory<null|string|{[key: string]: any}>| null, | ||||||
|   styles = styles || null; |     stylesInput?: {[key: string]: any} | BoundPlayerFactory<null|{[key: string]: any}>| | ||||||
|  |         null): void { | ||||||
|  |   stylesInput = stylesInput || null; | ||||||
|  | 
 | ||||||
|  |   const element = context[StylingIndex.ElementPosition] !as HTMLElement; | ||||||
|  |   const classesPlayerBuilder = classesInput instanceof BoundPlayerFactory ? | ||||||
|  |       new ClassAndStylePlayerBuilder(classesInput as any, element, BindingType.Class) : | ||||||
|  |       null; | ||||||
|  |   const stylesPlayerBuilder = stylesInput instanceof BoundPlayerFactory ? | ||||||
|  |       new ClassAndStylePlayerBuilder(stylesInput as any, element, BindingType.Style) : | ||||||
|  |       null; | ||||||
|  | 
 | ||||||
|  |   const classesValue = classesPlayerBuilder ? | ||||||
|  |       (classesInput as BoundPlayerFactory<{[key: string]: any}|string>) !.value : | ||||||
|  |       classesInput; | ||||||
|  |   const stylesValue = stylesPlayerBuilder ? stylesInput !.value : stylesInput; | ||||||
|  | 
 | ||||||
|   // early exit (this is what's done to avoid using ctx.bind() to cache the value)
 |   // early exit (this is what's done to avoid using ctx.bind() to cache the value)
 | ||||||
|   const ignoreAllClassUpdates = classes === context[StylingIndex.PreviousMultiClassValue]; |   const ignoreAllClassUpdates = classesValue === context[StylingIndex.PreviousMultiClassValue]; | ||||||
|   const ignoreAllStyleUpdates = styles === context[StylingIndex.PreviousMultiStyleValue]; |   const ignoreAllStyleUpdates = stylesValue === context[StylingIndex.PreviousMultiStyleValue]; | ||||||
|   if (ignoreAllClassUpdates && ignoreAllStyleUpdates) return; |   if (ignoreAllClassUpdates && ignoreAllStyleUpdates) return; | ||||||
| 
 | 
 | ||||||
|  |   context[StylingIndex.PreviousMultiClassValue] = classesValue; | ||||||
|  |   context[StylingIndex.PreviousMultiStyleValue] = stylesValue; | ||||||
|  | 
 | ||||||
|   let classNames: string[] = EMPTY_ARR; |   let classNames: string[] = EMPTY_ARR; | ||||||
|   let applyAllClasses = false; |   let applyAllClasses = false; | ||||||
|  |   let playerBuildersAreDirty = false; | ||||||
|  | 
 | ||||||
|  |   const classesPlayerBuilderIndex = | ||||||
|  |       classesPlayerBuilder ? PlayerIndex.ClassMapPlayerBuilderPosition : 0; | ||||||
|  |   if (hasPlayerBuilderChanged( | ||||||
|  |           context, classesPlayerBuilder, PlayerIndex.ClassMapPlayerBuilderPosition)) { | ||||||
|  |     setPlayerBuilder(context, classesPlayerBuilder, PlayerIndex.ClassMapPlayerBuilderPosition); | ||||||
|  |     playerBuildersAreDirty = true; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const stylesPlayerBuilderIndex = | ||||||
|  |       stylesPlayerBuilder ? PlayerIndex.StyleMapPlayerBuilderPosition : 0; | ||||||
|  |   if (hasPlayerBuilderChanged( | ||||||
|  |           context, stylesPlayerBuilder, PlayerIndex.StyleMapPlayerBuilderPosition)) { | ||||||
|  |     setPlayerBuilder(context, stylesPlayerBuilder, PlayerIndex.StyleMapPlayerBuilderPosition); | ||||||
|  |     playerBuildersAreDirty = true; | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   // each time a string-based value pops up then it shouldn't require a deep
 |   // each time a string-based value pops up then it shouldn't require a deep
 | ||||||
|   // check of what's changed.
 |   // check of what's changed.
 | ||||||
|   if (!ignoreAllClassUpdates) { |   if (!ignoreAllClassUpdates) { | ||||||
|     context[StylingIndex.PreviousMultiClassValue] = classes; |     if (typeof classesValue == 'string') { | ||||||
|     if (typeof classes == 'string') { |       classNames = classesValue.split(/\s+/); | ||||||
|       classNames = classes.split(/\s+/); |  | ||||||
|       // this boolean is used to avoid having to create a key/value map of `true` values
 |       // 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
 |       // since a classname string implies that all those classes are added
 | ||||||
|       applyAllClasses = true; |       applyAllClasses = true; | ||||||
|     } else { |     } else { | ||||||
|       classNames = classes ? Object.keys(classes) : EMPTY_ARR; |       classNames = classesValue ? Object.keys(classesValue) : EMPTY_ARR; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   classes = (classes || EMPTY_OBJ) as{[key: string]: any}; |   const classes = (classesValue || EMPTY_OBJ) as{[key: string]: any}; | ||||||
| 
 |   const styleProps = stylesValue ? Object.keys(stylesValue) : EMPTY_ARR; | ||||||
|   if (!ignoreAllStyleUpdates) { |   const styles = stylesValue || EMPTY_OBJ; | ||||||
|     context[StylingIndex.PreviousMultiStyleValue] = styles; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   const styleProps = styles ? Object.keys(styles) : EMPTY_ARR; |  | ||||||
|   styles = styles || EMPTY_OBJ; |  | ||||||
| 
 | 
 | ||||||
|   const classesStartIndex = styleProps.length; |   const classesStartIndex = styleProps.length; | ||||||
|   const multiStartIndex = getMultiStartIndex(context); |   const multiStartIndex = getMultiStartIndex(context); | ||||||
| @ -213,13 +252,18 @@ export function updateStylingMap( | |||||||
|           isClassBased ? classNames[adjustedPropIndex] : styleProps[adjustedPropIndex]; |           isClassBased ? classNames[adjustedPropIndex] : styleProps[adjustedPropIndex]; | ||||||
|       const newValue: string|boolean = |       const newValue: string|boolean = | ||||||
|           isClassBased ? (applyAllClasses ? true : classes[newProp]) : styles[newProp]; |           isClassBased ? (applyAllClasses ? true : classes[newProp]) : styles[newProp]; | ||||||
|  |       const playerBuilderIndex = | ||||||
|  |           isClassBased ? classesPlayerBuilderIndex : stylesPlayerBuilderIndex; | ||||||
| 
 | 
 | ||||||
|       const prop = getProp(context, ctxIndex); |       const prop = getProp(context, ctxIndex); | ||||||
|       if (prop === newProp) { |       if (prop === newProp) { | ||||||
|         const value = getValue(context, ctxIndex); |         const value = getValue(context, ctxIndex); | ||||||
|         const flag = getPointers(context, ctxIndex); |         const flag = getPointers(context, ctxIndex); | ||||||
|  |         setPlayerBuilderIndex(context, ctxIndex, playerBuilderIndex); | ||||||
|  | 
 | ||||||
|         if (hasValueChanged(flag, value, newValue)) { |         if (hasValueChanged(flag, value, newValue)) { | ||||||
|           setValue(context, ctxIndex, newValue); |           setValue(context, ctxIndex, newValue); | ||||||
|  |           playerBuildersAreDirty = playerBuildersAreDirty || !!playerBuilderIndex; | ||||||
| 
 | 
 | ||||||
|           const initialValue = getInitialValue(context, flag); |           const initialValue = getInitialValue(context, flag); | ||||||
| 
 | 
 | ||||||
| @ -242,13 +286,16 @@ export function updateStylingMap( | |||||||
|             setValue(context, ctxIndex, newValue); |             setValue(context, ctxIndex, newValue); | ||||||
|             if (hasValueChanged(flagToCompare, initialValue, newValue)) { |             if (hasValueChanged(flagToCompare, initialValue, newValue)) { | ||||||
|               setDirty(context, ctxIndex, true); |               setDirty(context, ctxIndex, true); | ||||||
|  |               playerBuildersAreDirty = playerBuildersAreDirty || !!playerBuilderIndex; | ||||||
|               dirty = true; |               dirty = true; | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|         } else { |         } else { | ||||||
|           // we only care to do this if the insertion is in the middle
 |           // we only care to do this if the insertion is in the middle
 | ||||||
|           const newFlag = prepareInitialFlag(newProp, isClassBased, getStyleSanitizer(context)); |           const newFlag = prepareInitialFlag(newProp, isClassBased, getStyleSanitizer(context)); | ||||||
|           insertNewMultiProperty(context, ctxIndex, isClassBased, newProp, newFlag, newValue); |           playerBuildersAreDirty = playerBuildersAreDirty || !!playerBuilderIndex; | ||||||
|  |           insertNewMultiProperty( | ||||||
|  |               context, ctxIndex, isClassBased, newProp, newFlag, newValue, playerBuilderIndex); | ||||||
|           dirty = true; |           dirty = true; | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
| @ -272,6 +319,13 @@ export function updateStylingMap( | |||||||
|       if (doRemoveValue) { |       if (doRemoveValue) { | ||||||
|         setDirty(context, ctxIndex, true); |         setDirty(context, ctxIndex, true); | ||||||
|         setValue(context, ctxIndex, null); |         setValue(context, ctxIndex, null); | ||||||
|  | 
 | ||||||
|  |         // we keep the player factory the same so that the `nulled` value can
 | ||||||
|  |         // be instructed into the player because removing a style and/or a class
 | ||||||
|  |         // is a valid animation player instruction.
 | ||||||
|  |         const playerBuilderIndex = | ||||||
|  |             isClassBased ? classesPlayerBuilderIndex : stylesPlayerBuilderIndex; | ||||||
|  |         setPlayerBuilderIndex(context, ctxIndex, playerBuilderIndex); | ||||||
|         dirty = true; |         dirty = true; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| @ -292,7 +346,9 @@ export function updateStylingMap( | |||||||
|       const value: string|boolean = |       const value: string|boolean = | ||||||
|           isClassBased ? (applyAllClasses ? true : classes[prop]) : styles[prop]; |           isClassBased ? (applyAllClasses ? true : classes[prop]) : styles[prop]; | ||||||
|       const flag = prepareInitialFlag(prop, isClassBased, sanitizer) | StylingFlags.Dirty; |       const flag = prepareInitialFlag(prop, isClassBased, sanitizer) | StylingFlags.Dirty; | ||||||
|       context.push(flag, prop, value); |       const playerBuilderIndex = | ||||||
|  |           isClassBased ? classesPlayerBuilderIndex : stylesPlayerBuilderIndex; | ||||||
|  |       context.push(flag, prop, value, playerBuilderIndex); | ||||||
|       dirty = true; |       dirty = true; | ||||||
|     } |     } | ||||||
|     propIndex++; |     propIndex++; | ||||||
| @ -301,11 +357,15 @@ export function updateStylingMap( | |||||||
|   if (dirty) { |   if (dirty) { | ||||||
|     setContextDirty(context, true); |     setContextDirty(context, true); | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   if (playerBuildersAreDirty) { | ||||||
|  |     setContextPlayersDirty(context, true); | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Sets and resolves a single styling property/value on the provided `StylingContext` so |  * Sets and resolves a single styling property/value on the provided `StylingContext` so | ||||||
|  * that they can be applied to the element once `renderStyling` is called. |  * that they can be applied to the element once `renderStyleAndClassBindings` is called. | ||||||
|  * |  * | ||||||
|  * Note that prop-level styling values are considered higher priority than any styling that |  * 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 |  * has been applied using `updateStylingMap`, therefore, when styling values are rendered | ||||||
| @ -318,13 +378,34 @@ export function updateStylingMap( | |||||||
|  * @param value The CSS style value that will be assigned |  * @param value The CSS style value that will be assigned | ||||||
|  */ |  */ | ||||||
| export function updateStyleProp( | export function updateStyleProp( | ||||||
|     context: StylingContext, index: number, value: string | boolean | null): void { |     context: StylingContext, index: number, | ||||||
|  |     input: string | boolean | null | BoundPlayerFactory<string|boolean|null>): void { | ||||||
|   const singleIndex = StylingIndex.SingleStylesStartPosition + index * StylingIndex.Size; |   const singleIndex = StylingIndex.SingleStylesStartPosition + index * StylingIndex.Size; | ||||||
|   const currValue = getValue(context, singleIndex); |   const currValue = getValue(context, singleIndex); | ||||||
|   const currFlag = getPointers(context, singleIndex); |   const currFlag = getPointers(context, singleIndex); | ||||||
|  |   const value: string|boolean|null = (input instanceof BoundPlayerFactory) ? input.value : input; | ||||||
| 
 | 
 | ||||||
|   // didn't change ... nothing to make a note of
 |   // didn't change ... nothing to make a note of
 | ||||||
|   if (hasValueChanged(currFlag, currValue, value)) { |   if (hasValueChanged(currFlag, currValue, value)) { | ||||||
|  |     const isClassBased = (currFlag & StylingFlags.Class) === StylingFlags.Class; | ||||||
|  |     const element = context[StylingIndex.ElementPosition] !as HTMLElement; | ||||||
|  |     const playerBuilder = input instanceof BoundPlayerFactory ? | ||||||
|  |         new ClassAndStylePlayerBuilder( | ||||||
|  |             input as any, element, isClassBased ? BindingType.Class : BindingType.Style) : | ||||||
|  |         null; | ||||||
|  |     const value = (playerBuilder ? (input as BoundPlayerFactory<any>).value : input) as string | | ||||||
|  |         boolean | null; | ||||||
|  |     const currPlayerIndex = getPlayerBuilderIndex(context, singleIndex); | ||||||
|  | 
 | ||||||
|  |     let playerBuildersAreDirty = false; | ||||||
|  |     let playerBuilderIndex = playerBuilder ? currPlayerIndex : 0; | ||||||
|  |     if (hasPlayerBuilderChanged(context, playerBuilder, currPlayerIndex)) { | ||||||
|  |       const newIndex = setPlayerBuilder(context, playerBuilder, currPlayerIndex); | ||||||
|  |       playerBuilderIndex = playerBuilder ? newIndex : 0; | ||||||
|  |       setPlayerBuilderIndex(context, singleIndex, playerBuilderIndex); | ||||||
|  |       playerBuildersAreDirty = true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     // the value will always get updated (even if the dirty flag is skipped)
 |     // the value will always get updated (even if the dirty flag is skipped)
 | ||||||
|     setValue(context, singleIndex, value); |     setValue(context, singleIndex, value); | ||||||
|     const indexForMulti = getMultiOrSingleIndex(currFlag); |     const indexForMulti = getMultiOrSingleIndex(currFlag); | ||||||
| @ -335,8 +416,6 @@ export function updateStyleProp( | |||||||
|       let multiDirty = false; |       let multiDirty = false; | ||||||
|       let singleDirty = true; |       let singleDirty = true; | ||||||
| 
 | 
 | ||||||
|       const isClassBased = (currFlag & StylingFlags.Class) === StylingFlags.Class; |  | ||||||
| 
 |  | ||||||
|       // only when the value is set to `null` should the multi-value get flagged
 |       // only when the value is set to `null` should the multi-value get flagged
 | ||||||
|       if (!valueExists(value, isClassBased) && valueExists(valueForMulti, isClassBased)) { |       if (!valueExists(value, isClassBased) && valueExists(valueForMulti, isClassBased)) { | ||||||
|         multiDirty = true; |         multiDirty = true; | ||||||
| @ -347,6 +426,10 @@ export function updateStyleProp( | |||||||
|       setDirty(context, singleIndex, singleDirty); |       setDirty(context, singleIndex, singleDirty); | ||||||
|       setContextDirty(context, true); |       setContextDirty(context, true); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     if (playerBuildersAreDirty) { | ||||||
|  |       setContextPlayersDirty(context, true); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -360,7 +443,8 @@ export function updateStyleProp( | |||||||
|  * @param addOrRemove Whether or not to add or remove the CSS class |  * @param addOrRemove Whether or not to add or remove the CSS class | ||||||
|  */ |  */ | ||||||
| export function updateClassProp( | export function updateClassProp( | ||||||
|     context: StylingContext, index: number, addOrRemove: boolean): void { |     context: StylingContext, index: number, | ||||||
|  |     addOrRemove: boolean | BoundPlayerFactory<boolean>): void { | ||||||
|   const adjustedIndex = index + context[StylingIndex.ClassOffsetPosition]; |   const adjustedIndex = index + context[StylingIndex.ClassOffsetPosition]; | ||||||
|   updateStyleProp(context, adjustedIndex, addOrRemove); |   updateStyleProp(context, adjustedIndex, addOrRemove); | ||||||
| } | } | ||||||
| @ -378,15 +462,19 @@ export function updateClassProp( | |||||||
|  * @param context The styling context that will be used to determine |  * @param context The styling context that will be used to determine | ||||||
|  *      what styles will be rendered |  *      what styles will be rendered | ||||||
|  * @param renderer the renderer that will be used to apply the styling |  * @param renderer the renderer that will be used to apply the styling | ||||||
|  * @param styleStore if provided, the updated style values will be applied |  * @param classesStore if provided, the updated class values will be applied | ||||||
|  *    to this key/value map instead of being renderered via the renderer. |  *    to this key/value map instead of being renderered via the renderer. | ||||||
|  * @param classStore if provided, the updated class values will be applied |  * @param stylesStore if provided, the updated style values will be applied | ||||||
|  *    to this key/value map instead of being renderered via the renderer. |  *    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( | export function renderStyleAndClassBindings( | ||||||
|     context: StylingContext, renderer: Renderer3, styleStore?: {[key: string]: any}, |     context: StylingContext, renderer: Renderer3, rootOrView: RootContext | LViewData, | ||||||
|     classStore?: {[key: string]: boolean}) { |     classesStore?: BindingStore | null, stylesStore?: BindingStore | null): number { | ||||||
|  |   let totalPlayersQueued = 0; | ||||||
|   if (isContextDirty(context)) { |   if (isContextDirty(context)) { | ||||||
|  |     const flushPlayerBuilders: any = | ||||||
|  |         context[StylingIndex.MasterFlagPosition] & StylingFlags.PlayerBuildersDirty; | ||||||
|     const native = context[StylingIndex.ElementPosition] !; |     const native = context[StylingIndex.ElementPosition] !; | ||||||
|     const multiStartIndex = getMultiStartIndex(context); |     const multiStartIndex = getMultiStartIndex(context); | ||||||
|     const styleSanitizer = getStyleSanitizer(context); |     const styleSanitizer = getStyleSanitizer(context); | ||||||
| @ -397,6 +485,7 @@ export function renderStyling( | |||||||
|         const prop = getProp(context, i); |         const prop = getProp(context, i); | ||||||
|         const value = getValue(context, i); |         const value = getValue(context, i); | ||||||
|         const flag = getPointers(context, i); |         const flag = getPointers(context, i); | ||||||
|  |         const playerBuilder = getPlayerBuilder(context, i); | ||||||
|         const isClassBased = flag & StylingFlags.Class ? true : false; |         const isClassBased = flag & StylingFlags.Class ? true : false; | ||||||
|         const isInSingleRegion = i < multiStartIndex; |         const isInSingleRegion = i < multiStartIndex; | ||||||
| 
 | 
 | ||||||
| @ -422,17 +511,52 @@ export function renderStyling( | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (isClassBased) { |         if (isClassBased) { | ||||||
|           setClass(native, prop, valueToApply ? true : false, renderer, classStore); |           setClass( | ||||||
|  |               native, prop, valueToApply ? true : false, renderer, classesStore, playerBuilder); | ||||||
|         } else { |         } else { | ||||||
|           const sanitizer = (flag & StylingFlags.Sanitize) ? styleSanitizer : null; |           const sanitizer = (flag & StylingFlags.Sanitize) ? styleSanitizer : null; | ||||||
|           setStyle(native, prop, valueToApply as string | null, renderer, sanitizer, styleStore); |           setStyle( | ||||||
|  |               native, prop, valueToApply as string | null, renderer, sanitizer, stylesStore, | ||||||
|  |               playerBuilder); | ||||||
|         } |         } | ||||||
|         setDirty(context, i, false); |         setDirty(context, i, false); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if (flushPlayerBuilders) { | ||||||
|  |       const rootContext = | ||||||
|  |           Array.isArray(rootOrView) ? getRootContext(rootOrView) : rootOrView as RootContext; | ||||||
|  |       const playerContext = getPlayerContext(context) !; | ||||||
|  |       const playersStartIndex = playerContext[PlayerIndex.NonBuilderPlayersStart]; | ||||||
|  |       for (let i = PlayerIndex.PlayerBuildersStartPosition; i < playersStartIndex; | ||||||
|  |            i += PlayerIndex.PlayerAndPlayerBuildersTupleSize) { | ||||||
|  |         const builder = playerContext[i] as ClassAndStylePlayerBuilder<any>| null; | ||||||
|  |         const playerInsertionIndex = i + PlayerIndex.PlayerOffsetPosition; | ||||||
|  |         const oldPlayer = playerContext[playerInsertionIndex] as Player | null; | ||||||
|  |         if (builder) { | ||||||
|  |           const player = builder.buildPlayer(oldPlayer); | ||||||
|  |           if (player !== undefined) { | ||||||
|  |             if (player != null) { | ||||||
|  |               const wasQueued = addPlayerInternal( | ||||||
|  |                   playerContext, rootContext, native as HTMLElement, player, playerInsertionIndex); | ||||||
|  |               wasQueued && totalPlayersQueued++; | ||||||
|  |             } | ||||||
|  |             if (oldPlayer) { | ||||||
|  |               oldPlayer.destroy(); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } else if (oldPlayer) { | ||||||
|  |           // the player builder has been removed ... therefore we should delete the associated
 | ||||||
|  |           // player
 | ||||||
|  |           oldPlayer.destroy(); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       setContextPlayersDirty(context, false); | ||||||
|  |     } | ||||||
|     setContextDirty(context, false); |     setContextDirty(context, false); | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   return totalPlayersQueued; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -449,10 +573,16 @@ export function renderStyling( | |||||||
|  */ |  */ | ||||||
| function setStyle( | function setStyle( | ||||||
|     native: any, prop: string, value: string | null, renderer: Renderer3, |     native: any, prop: string, value: string | null, renderer: Renderer3, | ||||||
|     sanitizer: StyleSanitizeFn | null, store?: {[key: string]: any}) { |     sanitizer: StyleSanitizeFn | null, store?: BindingStore | null, | ||||||
|  |     playerBuilder?: ClassAndStylePlayerBuilder<any>| null) { | ||||||
|   value = sanitizer && value ? sanitizer(prop, value) : value; |   value = sanitizer && value ? sanitizer(prop, value) : value; | ||||||
|   if (store) { |   if (store || playerBuilder) { | ||||||
|     store[prop] = value; |     if (store) { | ||||||
|  |       store.setValue(prop, value); | ||||||
|  |     } | ||||||
|  |     if (playerBuilder) { | ||||||
|  |       playerBuilder.setValue(prop, value); | ||||||
|  |     } | ||||||
|   } else if (value) { |   } else if (value) { | ||||||
|     ngDevMode && ngDevMode.rendererSetStyle++; |     ngDevMode && ngDevMode.rendererSetStyle++; | ||||||
|     isProceduralRenderer(renderer) ? |     isProceduralRenderer(renderer) ? | ||||||
| @ -479,10 +609,15 @@ function setStyle( | |||||||
|  * @param store an optional key/value map that will be used as a context to render styles on |  * @param store an optional key/value map that will be used as a context to render styles on | ||||||
|  */ |  */ | ||||||
| function setClass( | function setClass( | ||||||
|     native: any, className: string, add: boolean, renderer: Renderer3, |     native: any, className: string, add: boolean, renderer: Renderer3, store?: BindingStore | null, | ||||||
|     store?: {[key: string]: boolean}) { |     playerBuilder?: ClassAndStylePlayerBuilder<any>| null) { | ||||||
|   if (store) { |   if (store || playerBuilder) { | ||||||
|     store[className] = add; |     if (store) { | ||||||
|  |       store.setValue(className, add); | ||||||
|  |     } | ||||||
|  |     if (playerBuilder) { | ||||||
|  |       playerBuilder.setValue(className, add); | ||||||
|  |     } | ||||||
|   } else if (add) { |   } else if (add) { | ||||||
|     ngDevMode && ngDevMode.rendererAddClass++; |     ngDevMode && ngDevMode.rendererAddClass++; | ||||||
|     isProceduralRenderer(renderer) ? renderer.addClass(native, className) : |     isProceduralRenderer(renderer) ? renderer.addClass(native, className) : | ||||||
| @ -558,6 +693,54 @@ function setValue(context: StylingContext, index: number, value: string | null | | |||||||
|   context[index + StylingIndex.ValueOffset] = value; |   context[index + StylingIndex.ValueOffset] = value; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function hasPlayerBuilderChanged( | ||||||
|  |     context: StylingContext, builder: ClassAndStylePlayerBuilder<any>| null, index: number) { | ||||||
|  |   const playerContext = context[StylingIndex.PlayerContext] !; | ||||||
|  |   if (builder) { | ||||||
|  |     if (!playerContext || index === 0) { | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |   } else if (!playerContext) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   return playerContext[index] !== builder; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function setPlayerBuilder( | ||||||
|  |     context: StylingContext, builder: ClassAndStylePlayerBuilder<any>| null, | ||||||
|  |     insertionIndex: number): number { | ||||||
|  |   let playerContext = context[StylingIndex.PlayerContext] || allocPlayerContext(context); | ||||||
|  |   if (insertionIndex > 0) { | ||||||
|  |     playerContext[insertionIndex] = builder; | ||||||
|  |   } else { | ||||||
|  |     insertionIndex = playerContext[PlayerIndex.NonBuilderPlayersStart]; | ||||||
|  |     playerContext.splice(insertionIndex, 0, builder, null); | ||||||
|  |     playerContext[PlayerIndex.NonBuilderPlayersStart] += | ||||||
|  |         PlayerIndex.PlayerAndPlayerBuildersTupleSize; | ||||||
|  |   } | ||||||
|  |   return insertionIndex; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function setPlayerBuilderIndex(context: StylingContext, index: number, playerBuilderIndex: number) { | ||||||
|  |   context[index + StylingIndex.PlayerBuilderIndexOffset] = playerBuilderIndex; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function getPlayerBuilderIndex(context: StylingContext, index: number): number { | ||||||
|  |   return (context[index + StylingIndex.PlayerBuilderIndexOffset] as number) || 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function getPlayerBuilder(context: StylingContext, index: number): ClassAndStylePlayerBuilder<any>| | ||||||
|  |     null { | ||||||
|  |   const playerBuilderIndex = getPlayerBuilderIndex(context, index); | ||||||
|  |   if (playerBuilderIndex) { | ||||||
|  |     const playerContext = context[StylingIndex.PlayerContext]; | ||||||
|  |     if (playerContext) { | ||||||
|  |       return playerContext[playerBuilderIndex] as ClassAndStylePlayerBuilder<any>| null; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return null; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| function setFlag(context: StylingContext, index: number, flag: number) { | function setFlag(context: StylingContext, index: number, flag: number) { | ||||||
|   const adjustedIndex = |   const adjustedIndex = | ||||||
|       index === StylingIndex.MasterFlagPosition ? index : (index + StylingIndex.FlagsOffset); |       index === StylingIndex.MasterFlagPosition ? index : (index + StylingIndex.FlagsOffset); | ||||||
| @ -586,6 +769,14 @@ export function setContextDirty(context: StylingContext, isDirtyYes: boolean): v | |||||||
|   setDirty(context, StylingIndex.MasterFlagPosition, isDirtyYes); |   setDirty(context, StylingIndex.MasterFlagPosition, isDirtyYes); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export function setContextPlayersDirty(context: StylingContext, isDirtyYes: boolean): void { | ||||||
|  |   if (isDirtyYes) { | ||||||
|  |     (context[StylingIndex.MasterFlagPosition] as number) |= StylingFlags.PlayerBuildersDirty; | ||||||
|  |   } else { | ||||||
|  |     (context[StylingIndex.MasterFlagPosition] as number) &= ~StylingFlags.PlayerBuildersDirty; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| function findEntryPositionByProp( | function findEntryPositionByProp( | ||||||
|     context: StylingContext, prop: string, startIndex?: number): number { |     context: StylingContext, prop: string, startIndex?: number): number { | ||||||
|   for (let i = (startIndex || 0) + StylingIndex.PropertyOffset; i < context.length; |   for (let i = (startIndex || 0) + StylingIndex.PropertyOffset; i < context.length; | ||||||
| @ -602,6 +793,7 @@ function swapMultiContextEntries(context: StylingContext, indexA: number, indexB | |||||||
|   const tmpValue = getValue(context, indexA); |   const tmpValue = getValue(context, indexA); | ||||||
|   const tmpProp = getProp(context, indexA); |   const tmpProp = getProp(context, indexA); | ||||||
|   const tmpFlag = getPointers(context, indexA); |   const tmpFlag = getPointers(context, indexA); | ||||||
|  |   const tmpPlayerBuilderIndex = getPlayerBuilderIndex(context, indexA); | ||||||
| 
 | 
 | ||||||
|   let flagA = tmpFlag; |   let flagA = tmpFlag; | ||||||
|   let flagB = getPointers(context, indexB); |   let flagB = getPointers(context, indexB); | ||||||
| @ -623,10 +815,12 @@ function swapMultiContextEntries(context: StylingContext, indexA: number, indexB | |||||||
|   setValue(context, indexA, getValue(context, indexB)); |   setValue(context, indexA, getValue(context, indexB)); | ||||||
|   setProp(context, indexA, getProp(context, indexB)); |   setProp(context, indexA, getProp(context, indexB)); | ||||||
|   setFlag(context, indexA, getPointers(context, indexB)); |   setFlag(context, indexA, getPointers(context, indexB)); | ||||||
|  |   setPlayerBuilderIndex(context, indexA, getPlayerBuilderIndex(context, indexB)); | ||||||
| 
 | 
 | ||||||
|   setValue(context, indexB, tmpValue); |   setValue(context, indexB, tmpValue); | ||||||
|   setProp(context, indexB, tmpProp); |   setProp(context, indexB, tmpProp); | ||||||
|   setFlag(context, indexB, tmpFlag); |   setFlag(context, indexB, tmpFlag); | ||||||
|  |   setPlayerBuilderIndex(context, indexB, tmpPlayerBuilderIndex); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function updateSinglePointerValues(context: StylingContext, indexStartPosition: number) { | function updateSinglePointerValues(context: StylingContext, indexStartPosition: number) { | ||||||
| @ -647,13 +841,13 @@ function updateSinglePointerValues(context: StylingContext, indexStartPosition: | |||||||
| 
 | 
 | ||||||
| function insertNewMultiProperty( | function insertNewMultiProperty( | ||||||
|     context: StylingContext, index: number, classBased: boolean, name: string, flag: number, |     context: StylingContext, index: number, classBased: boolean, name: string, flag: number, | ||||||
|     value: string | boolean): void { |     value: string | boolean, playerIndex: number): void { | ||||||
|   const doShift = index < context.length; |   const doShift = index < context.length; | ||||||
| 
 | 
 | ||||||
|   // prop does not exist in the list, add it in
 |   // prop does not exist in the list, add it in
 | ||||||
|   context.splice( |   context.splice( | ||||||
|       index, 0, flag | StylingFlags.Dirty | (classBased ? StylingFlags.Class : StylingFlags.None), |       index, 0, flag | StylingFlags.Dirty | (classBased ? StylingFlags.Class : StylingFlags.None), | ||||||
|       name, value); |       name, value, playerIndex); | ||||||
| 
 | 
 | ||||||
|   if (doShift) { |   if (doShift) { | ||||||
|     // because the value was inserted midway into the array then we
 |     // because the value was inserted midway into the array then we
 | ||||||
| @ -696,3 +890,35 @@ function hasValueChanged( | |||||||
|   // everything else is safe to check with a normal equality check
 |   // everything else is safe to check with a normal equality check
 | ||||||
|   return a !== b; |   return a !== b; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export class ClassAndStylePlayerBuilder<T> implements PlayerBuilder { | ||||||
|  |   private _values: {[key: string]: string | null} = {}; | ||||||
|  |   private _dirty = false; | ||||||
|  |   private _factory: BoundPlayerFactory<T>; | ||||||
|  | 
 | ||||||
|  |   constructor(factory: PlayerFactory, private _element: HTMLElement, private _type: BindingType) { | ||||||
|  |     this._factory = factory as any; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   setValue(prop: string, value: any) { | ||||||
|  |     if (this._values[prop] !== value) { | ||||||
|  |       this._values[prop] = value; | ||||||
|  |       this._dirty = true; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   buildPlayer(currentPlayer?: Player|null): Player|undefined|null { | ||||||
|  |     // if no values have been set here then this means the binding didn't
 | ||||||
|  |     // change and therefore the binding values were not updated through
 | ||||||
|  |     // `setValue` which means no new player will be provided.
 | ||||||
|  |     if (this._dirty) { | ||||||
|  |       const player = | ||||||
|  |           this._factory.fn(this._element, this._type, this._values !, currentPlayer || null); | ||||||
|  |       this._values = {}; | ||||||
|  |       this._dirty = false; | ||||||
|  |       return player; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return undefined; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ | |||||||
|  * Use of this source code is governed by an MIT-style license that can be |  * 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
 |  * found in the LICENSE file at https://angular.io/license
 | ||||||
|  */ |  */ | ||||||
| import {Player, PlayerHandler} from '../interfaces/player'; | import {PlayState, Player, PlayerHandler} from '../interfaces/player'; | ||||||
| 
 | 
 | ||||||
| export class CorePlayerHandler implements PlayerHandler { | export class CorePlayerHandler implements PlayerHandler { | ||||||
|   private _players: Player[] = []; |   private _players: Player[] = []; | ||||||
| @ -13,7 +13,7 @@ export class CorePlayerHandler implements PlayerHandler { | |||||||
|   flushPlayers() { |   flushPlayers() { | ||||||
|     for (let i = 0; i < this._players.length; i++) { |     for (let i = 0; i < this._players.length; i++) { | ||||||
|       const player = this._players[i]; |       const player = this._players[i]; | ||||||
|       if (!player.parent) { |       if (!player.parent && player.state === PlayState.Pending) { | ||||||
|         player.play(); |         player.play(); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  | |||||||
							
								
								
									
										33
									
								
								packages/core/src/render3/styling/player_factory.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								packages/core/src/render3/styling/player_factory.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | |||||||
|  | /** | ||||||
|  |  * @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 {PlayerFactory, PlayerFactoryBuildFn} from '../interfaces/player'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Combines the binding value and a factory for an animation player. | ||||||
|  |  * | ||||||
|  |  * Used to bind a player to an element template binding (currently only | ||||||
|  |  * `[style]`, `[style.prop]`, `[class]` and `[class.name]` bindings | ||||||
|  |  * supported). The provided `factoryFn` function will be run once all | ||||||
|  |  * the associated bindings have been evaluated on the element and is | ||||||
|  |  * designed to return a player which will then be placed on the element. | ||||||
|  |  * | ||||||
|  |  * @param factoryFn The function that is used to create a player | ||||||
|  |  *   once all the rendering-related (styling values) have been | ||||||
|  |  *   processed for the element binding. | ||||||
|  |  * @param value The raw value that will be exposed to the binding | ||||||
|  |  *   so that the binding can update its internal values when | ||||||
|  |  *   any changes are evaluated. | ||||||
|  |  */ | ||||||
|  | export function bindPlayerFactory<T>(factoryFn: PlayerFactoryBuildFn, value: T): PlayerFactory { | ||||||
|  |   return new BoundPlayerFactory(factoryFn, value) as any; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class BoundPlayerFactory<T> { | ||||||
|  |   '__brand__': 'Brand for PlayerFactory that nothing will match'; | ||||||
|  |   constructor(public fn: PlayerFactoryBuildFn, public value: T) {} | ||||||
|  | } | ||||||
| @ -5,19 +5,19 @@ | |||||||
|  * Use of this source code is governed by an MIT-style license that can be |  * 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
 |  * found in the LICENSE file at https://angular.io/license
 | ||||||
|  */ |  */ | ||||||
|  | import '../ng_dev_mode'; | ||||||
| 
 | 
 | ||||||
| import {StyleSanitizeFn} from '../../sanitization/style_sanitizer'; | import {StyleSanitizeFn} from '../../sanitization/style_sanitizer'; | ||||||
| import {getContext} from '../context_discovery'; | import {getContext} from '../context_discovery'; | ||||||
| import {ACTIVE_INDEX, LContainer} from '../interfaces/container'; | import {ACTIVE_INDEX, LContainer} from '../interfaces/container'; | ||||||
| import {LContext} from '../interfaces/context'; | import {LContext} from '../interfaces/context'; | ||||||
| import {PlayerContext} from '../interfaces/player'; | import {PlayState, Player, PlayerContext, PlayerIndex} from '../interfaces/player'; | ||||||
| import {RElement} from '../interfaces/renderer'; | import {RElement} from '../interfaces/renderer'; | ||||||
| import {InitialStyles, StylingContext, StylingIndex} from '../interfaces/styling'; | import {InitialStyles, StylingContext, StylingIndex} from '../interfaces/styling'; | ||||||
| import {FLAGS, HEADER_OFFSET, HOST, LViewData} from '../interfaces/view'; | import {FLAGS, HEADER_OFFSET, HOST, LViewData, RootContext} from '../interfaces/view'; | ||||||
| import {getTNode} from '../util'; | import {getTNode} from '../util'; | ||||||
| 
 | 
 | ||||||
| export const EMPTY_ARR: any[] = []; | import {CorePlayerHandler} from './core_player_handler'; | ||||||
| export const EMPTY_OBJ: {[key: string]: any} = {}; |  | ||||||
| 
 | 
 | ||||||
| export function createEmptyStylingContext( | export function createEmptyStylingContext( | ||||||
|     element?: RElement | null, sanitizer?: StyleSanitizeFn | null, |     element?: RElement | null, sanitizer?: StyleSanitizeFn | null, | ||||||
| @ -75,7 +75,10 @@ export function getStylingContext(index: number, viewData: LViewData): StylingCo | |||||||
|     // This is an LViewData or an LContainer
 |     // This is an LViewData or an LContainer
 | ||||||
|     const stylingTemplate = getTNode(index, viewData).stylingTemplate; |     const stylingTemplate = getTNode(index, viewData).stylingTemplate; | ||||||
| 
 | 
 | ||||||
|     if (wrapper !== viewData) storageIndex = HOST; |     if (wrapper !== viewData) { | ||||||
|  |       storageIndex = HOST; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     return wrapper[storageIndex] = stylingTemplate ? |     return wrapper[storageIndex] = stylingTemplate ? | ||||||
|         allocStylingContext(slotValue, stylingTemplate) : |         allocStylingContext(slotValue, stylingTemplate) : | ||||||
|         createEmptyStylingContext(slotValue); |         createEmptyStylingContext(slotValue); | ||||||
| @ -87,18 +90,88 @@ function isStylingContext(value: LViewData | LContainer | StylingContext) { | |||||||
|   return typeof value[FLAGS] !== 'number' && typeof value[ACTIVE_INDEX] !== 'number'; |   return typeof value[FLAGS] !== 'number' && typeof value[ACTIVE_INDEX] !== 'number'; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function getOrCreatePlayerContext(target: {}, context?: LContext | null): PlayerContext { | 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 || getContext(target) !; |   context = context || getContext(target) !; | ||||||
|   if (ngDevMode && !context) { |   if (!context) { | ||||||
|     throw new Error( |     ngDevMode && throwInvalidRefError(); | ||||||
|         'Only elements that exist in an Angular application can be used for player access'); |     return null; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   const {lViewData, nodeIndex} = context; |   const {lViewData, nodeIndex} = context; | ||||||
|   const stylingContext = getStylingContext(nodeIndex - HEADER_OFFSET, lViewData); |   const stylingContext = getStylingContext(nodeIndex - HEADER_OFFSET, lViewData); | ||||||
|   return stylingContext[StylingIndex.PlayerContext] || allocPlayerContext(stylingContext); |   return getPlayerContext(stylingContext) || allocPlayerContext(stylingContext); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function allocPlayerContext(data: StylingContext): PlayerContext { | export function getPlayerContext(stylingContext: StylingContext): PlayerContext|null { | ||||||
|   return data[StylingIndex.PlayerContext] = []; |   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'); | ||||||
| } | } | ||||||
|  | |||||||
| @ -8,6 +8,7 @@ ng_module( | |||||||
|     name = "animation_world", |     name = "animation_world", | ||||||
|     srcs = ["index.ts"], |     srcs = ["index.ts"], | ||||||
|     tags = ["ivy-only"], |     tags = ["ivy-only"], | ||||||
|  |     type_check = False,  # see #26462 | ||||||
|     deps = [ |     deps = [ | ||||||
|         "//packages/common", |         "//packages/common", | ||||||
|         "//packages/core", |         "//packages/core", | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ | |||||||
|     "name": "AnimationWorldComponent" |     "name": "AnimationWorldComponent" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "name": "AnimationWorldComponent_div_Template_4" |     "name": "AnimationWorldComponent_div_Template_6" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "name": "BINDING_INDEX" |     "name": "BINDING_INDEX" | ||||||
| @ -14,6 +14,9 @@ | |||||||
|   { |   { | ||||||
|     "name": "BLOOM_MASK" |     "name": "BLOOM_MASK" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "BoundPlayerFactory" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "CIRCULAR$1" |     "name": "CIRCULAR$1" | ||||||
|   }, |   }, | ||||||
| @ -32,6 +35,9 @@ | |||||||
|   { |   { | ||||||
|     "name": "ChangeDetectionStrategy" |     "name": "ChangeDetectionStrategy" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "ClassAndStylePlayerBuilder" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "CorePlayerHandler" |     "name": "CorePlayerHandler" | ||||||
|   }, |   }, | ||||||
| @ -212,6 +218,9 @@ | |||||||
|   { |   { | ||||||
|     "name": "ViewRef" |     "name": "ViewRef" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "WebAnimationsPlayer" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "_CLEAN_PROMISE" |     "name": "_CLEAN_PROMISE" | ||||||
|   }, |   }, | ||||||
| @ -251,6 +260,9 @@ | |||||||
|   { |   { | ||||||
|     "name": "_c3" |     "name": "_c3" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "_c4" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "_currentInjector" |     "name": "_currentInjector" | ||||||
|   }, |   }, | ||||||
| @ -278,6 +290,9 @@ | |||||||
|   { |   { | ||||||
|     "name": "addPlayer" |     "name": "addPlayer" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "addPlayerInternal" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "addRemoveViewFromContainer" |     "name": "addRemoveViewFromContainer" | ||||||
|   }, |   }, | ||||||
| @ -290,6 +305,9 @@ | |||||||
|   { |   { | ||||||
|     "name": "allocStylingContext" |     "name": "allocStylingContext" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "animateStyleFactory" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "appendChild" |     "name": "appendChild" | ||||||
|   }, |   }, | ||||||
| @ -302,6 +320,9 @@ | |||||||
|   { |   { | ||||||
|     "name": "bind" |     "name": "bind" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "bindPlayerFactory" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "bindingRootIndex" |     "name": "bindingRootIndex" | ||||||
|   }, |   }, | ||||||
| @ -644,6 +665,15 @@ | |||||||
|   { |   { | ||||||
|     "name": "getPipeDef" |     "name": "getPipeDef" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "getPlayerBuilder" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     "name": "getPlayerBuilderIndex" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     "name": "getPlayerContext" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "getPointers" |     "name": "getPointers" | ||||||
|   }, |   }, | ||||||
| @ -671,9 +701,15 @@ | |||||||
|   { |   { | ||||||
|     "name": "getRootContext" |     "name": "getRootContext" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "getRootContext$2" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "getRootView" |     "name": "getRootView" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "getRootView$1" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "getStyleSanitizer" |     "name": "getStyleSanitizer" | ||||||
|   }, |   }, | ||||||
| @ -698,6 +734,9 @@ | |||||||
|   { |   { | ||||||
|     "name": "getValue" |     "name": "getValue" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "hasPlayerBuilderChanged" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "hasValueChanged" |     "name": "hasValueChanged" | ||||||
|   }, |   }, | ||||||
| @ -797,6 +836,9 @@ | |||||||
|   { |   { | ||||||
|     "name": "listener" |     "name": "listener" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "loadContext" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "locateHostElement" |     "name": "locateHostElement" | ||||||
|   }, |   }, | ||||||
| @ -809,6 +851,9 @@ | |||||||
|   { |   { | ||||||
|     "name": "makeParamDecorator" |     "name": "makeParamDecorator" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "markDirty" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "markDirtyIfOnPush" |     "name": "markDirtyIfOnPush" | ||||||
|   }, |   }, | ||||||
| @ -900,7 +945,7 @@ | |||||||
|     "name": "renderEmbeddedTemplate" |     "name": "renderEmbeddedTemplate" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "name": "renderStyling" |     "name": "renderStyleAndClassBindings" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "name": "resetComponentState" |     "name": "resetComponentState" | ||||||
| @ -932,6 +977,9 @@ | |||||||
|   { |   { | ||||||
|     "name": "setContextDirty" |     "name": "setContextDirty" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "setContextPlayersDirty" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "setCurrentInjector" |     "name": "setCurrentInjector" | ||||||
|   }, |   }, | ||||||
| @ -953,6 +1001,12 @@ | |||||||
|   { |   { | ||||||
|     "name": "setInputsFromAttrs" |     "name": "setInputsFromAttrs" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "setPlayerBuilder" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     "name": "setPlayerBuilderIndex" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "setProp" |     "name": "setProp" | ||||||
|   }, |   }, | ||||||
|  | |||||||
| @ -9,16 +9,19 @@ | |||||||
| import '@angular/core/test/bundling/util/src/reflect_metadata'; | import '@angular/core/test/bundling/util/src/reflect_metadata'; | ||||||
| 
 | 
 | ||||||
| import {CommonModule} from '@angular/common'; | import {CommonModule} from '@angular/common'; | ||||||
| import {Component, ElementRef, NgModule, ɵPlayState as PlayState, ɵPlayer as Player, ɵPlayerHandler as PlayerHandler, ɵaddPlayer as addPlayer, ɵrenderComponent as renderComponent} from '@angular/core'; | import {Component, ElementRef, NgModule, ɵPlayState as PlayState, ɵPlayer as Player, ɵPlayerHandler as PlayerHandler, ɵaddPlayer as addPlayer, ɵbindPlayerFactory as bindPlayerFactory, ɵmarkDirty as markDirty, ɵrenderComponent as renderComponent} from '@angular/core'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'animation-world', |   selector: 'animation-world', | ||||||
|   template: ` |   template: ` | ||||||
|     <nav> |     <nav> | ||||||
|       <button (click)="doAnimate()">Populate List</button> |       <button (click)="animateWithCustomPlayer()">Animate List (custom player)</button> | ||||||
|  |       <button (click)="animateWithStyles()">Populate List (style bindings)</button> | ||||||
|     </nav> |     </nav> | ||||||
|     <div class="list"> |     <div class="list"> | ||||||
|       <div *ngFor="let item of items" class="record" [class]="makeClass(item)"> |       <div | ||||||
|  |         *ngFor="let item of items" class="record" [class]="makeClass(item)" style="border-radius: 10px" | ||||||
|  |         [style]="styles"> | ||||||
|         {{ item }} |         {{ item }} | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
| @ -27,12 +30,18 @@ import {Component, ElementRef, NgModule, ɵPlayState as PlayState, ɵPlayer as P | |||||||
| class AnimationWorldComponent { | class AnimationWorldComponent { | ||||||
|   items: any[] = [1, 2, 3, 4, 5, 6, 7, 8, 9]; |   items: any[] = [1, 2, 3, 4, 5, 6, 7, 8, 9]; | ||||||
|   private _hostElement: HTMLElement; |   private _hostElement: HTMLElement; | ||||||
|  |   public styles: {[key: string]: any}|null = null; | ||||||
| 
 | 
 | ||||||
|   constructor(element: ElementRef) { this._hostElement = element.nativeElement; } |   constructor(element: ElementRef) { this._hostElement = element.nativeElement; } | ||||||
| 
 | 
 | ||||||
|   makeClass(index: number) { return `record-${index}`; } |   makeClass(index: number) { return `record-${index}`; } | ||||||
| 
 | 
 | ||||||
|   doAnimate() { |   animateWithStyles() { | ||||||
|  |     this.styles = animateStyleFactory([{opacity: 0}, {opacity: 1}], 300, 'ease-out'); | ||||||
|  |     markDirty(this); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   animateWithCustomPlayer() { | ||||||
|     const elements = this._hostElement.querySelectorAll('div.record') as any as HTMLElement[]; |     const elements = this._hostElement.querySelectorAll('div.record') as any as HTMLElement[]; | ||||||
|     for (let i = 0; i < elements.length; i++) { |     for (let i = 0; i < elements.length; i++) { | ||||||
|       const element = elements[i]; |       const element = elements[i]; | ||||||
| @ -55,13 +64,13 @@ function buildAnimationPlayer(element: HTMLElement, animationName: string, time: | |||||||
| class SimpleKeyframePlayer implements Player { | class SimpleKeyframePlayer implements Player { | ||||||
|   state = PlayState.Pending; |   state = PlayState.Pending; | ||||||
|   parent: Player|null = null; |   parent: Player|null = null; | ||||||
|   private _animationStyle: string; |   private _animationStyle: string = ''; | ||||||
|   private _listeners: {[stateName: string]: (() => any)[]} = {}; |   private _listeners: {[stateName: string]: (() => any)[]} = {}; | ||||||
|   constructor(private _element: HTMLElement, private _animationName: string, time: string) { |   constructor(private _element: HTMLElement, private _animationName: string, time: string) { | ||||||
|     this._animationStyle = `${time} ${_animationName}`; |     this._animationStyle = `${time} ${_animationName}`; | ||||||
|   } |   } | ||||||
|   private _start() { |   private _start() { | ||||||
|     this._element.style.animation = this._animationStyle; |     (this._element as any).style.animation = this._animationStyle; | ||||||
|     const animationFn = (event: AnimationEvent) => { |     const animationFn = (event: AnimationEvent) => { | ||||||
|       if (event.animationName == this._animationName) { |       if (event.animationName == this._animationName) { | ||||||
|         this._element.removeEventListener('animationend', animationFn); |         this._element.removeEventListener('animationend', animationFn); | ||||||
| @ -134,3 +143,66 @@ class AnimationDebugger implements PlayerHandler { | |||||||
| 
 | 
 | ||||||
| const playerHandler = new AnimationDebugger(); | const playerHandler = new AnimationDebugger(); | ||||||
| renderComponent(AnimationWorldComponent, {playerHandler}); | renderComponent(AnimationWorldComponent, {playerHandler}); | ||||||
|  | 
 | ||||||
|  | function animateStyleFactory(keyframes: any[], duration: number, easing: string) { | ||||||
|  |   const limit = keyframes.length - 1; | ||||||
|  |   const finalKeyframe = keyframes[limit]; | ||||||
|  |   return bindPlayerFactory((element: HTMLElement, type: number, values: {[key: string]: any}) => { | ||||||
|  |     const kf = keyframes.slice(0, limit); | ||||||
|  |     kf.push(values); | ||||||
|  |     return new WebAnimationsPlayer(element, keyframes, duration, easing); | ||||||
|  |   }, finalKeyframe); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class WebAnimationsPlayer implements Player { | ||||||
|  |   state = PlayState.Pending; | ||||||
|  |   parent: Player|null = null; | ||||||
|  |   private _listeners: {[stateName: string]: (() => any)[]} = {}; | ||||||
|  |   constructor( | ||||||
|  |       private _element: HTMLElement, private _keyframes: {[key: string]: any}[], | ||||||
|  |       private _duration: number, private _easing: string) {} | ||||||
|  |   private _start() { | ||||||
|  |     const player = this._element.animate( | ||||||
|  |         this._keyframes as any[], {duration: this._duration, easing: this._easing, fill: 'both'}); | ||||||
|  |     player.addEventListener('finish', e => { this.finish(); }); | ||||||
|  |   } | ||||||
|  |   addEventListener(state: PlayState|string, cb: () => any): void { | ||||||
|  |     const key = state.toString(); | ||||||
|  |     const arr = this._listeners[key] = (this._listeners[key] || []); | ||||||
|  |     arr.push(cb); | ||||||
|  |   } | ||||||
|  |   play(): void { | ||||||
|  |     if (this.state <= PlayState.Pending) { | ||||||
|  |       this._start(); | ||||||
|  |     } | ||||||
|  |     if (this.state != PlayState.Running) { | ||||||
|  |       this.state = PlayState.Running; | ||||||
|  |       this._emit(this.state); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   pause(): void { | ||||||
|  |     if (this.state != PlayState.Paused) { | ||||||
|  |       this.state = PlayState.Paused; | ||||||
|  |       this._emit(this.state); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   finish(): void { | ||||||
|  |     if (this.state < PlayState.Finished) { | ||||||
|  |       this._element.style.animation = ''; | ||||||
|  |       this.state = PlayState.Finished; | ||||||
|  |       this._emit(this.state); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   destroy(): void { | ||||||
|  |     if (this.state < PlayState.Destroyed) { | ||||||
|  |       this.finish(); | ||||||
|  |       this.state = PlayState.Destroyed; | ||||||
|  |       this._emit(this.state); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   capture(): any {} | ||||||
|  |   private _emit(state: PlayState) { | ||||||
|  |     const arr = this._listeners[state.toString()] || []; | ||||||
|  |     arr.forEach(cb => cb()); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | |||||||
| @ -8,6 +8,9 @@ | |||||||
|   { |   { | ||||||
|     "name": "BLOOM_MASK" |     "name": "BLOOM_MASK" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "BoundPlayerFactory" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "CIRCULAR$1" |     "name": "CIRCULAR$1" | ||||||
|   }, |   }, | ||||||
| @ -26,6 +29,12 @@ | |||||||
|   { |   { | ||||||
|     "name": "ChangeDetectionStrategy" |     "name": "ChangeDetectionStrategy" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "ClassAndStylePlayerBuilder" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     "name": "CorePlayerHandler" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "DECLARATION_VIEW" |     "name": "DECLARATION_VIEW" | ||||||
|   }, |   }, | ||||||
| @ -344,12 +353,18 @@ | |||||||
|   { |   { | ||||||
|     "name": "addComponentLogic" |     "name": "addComponentLogic" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "addPlayerInternal" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "addRemoveViewFromContainer" |     "name": "addRemoveViewFromContainer" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "name": "addToViewTree" |     "name": "addToViewTree" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "allocPlayerContext" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "allocStylingContext" |     "name": "allocStylingContext" | ||||||
|   }, |   }, | ||||||
| @ -686,6 +701,15 @@ | |||||||
|   { |   { | ||||||
|     "name": "getPipeDef" |     "name": "getPipeDef" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "getPlayerBuilder" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     "name": "getPlayerBuilderIndex" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     "name": "getPlayerContext" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "getPointers" |     "name": "getPointers" | ||||||
|   }, |   }, | ||||||
| @ -710,6 +734,12 @@ | |||||||
|   { |   { | ||||||
|     "name": "getRendererFactory" |     "name": "getRendererFactory" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "getRootContext" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     "name": "getRootView" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "getStyleSanitizer" |     "name": "getStyleSanitizer" | ||||||
|   }, |   }, | ||||||
| @ -734,6 +764,9 @@ | |||||||
|   { |   { | ||||||
|     "name": "getValue" |     "name": "getValue" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "hasPlayerBuilderChanged" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "hasValueChanged" |     "name": "hasValueChanged" | ||||||
|   }, |   }, | ||||||
| @ -930,7 +963,7 @@ | |||||||
|     "name": "renderEmbeddedTemplate" |     "name": "renderEmbeddedTemplate" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "name": "renderStyling" |     "name": "renderStyleAndClassBindings" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "name": "resetComponentState" |     "name": "resetComponentState" | ||||||
| @ -962,6 +995,9 @@ | |||||||
|   { |   { | ||||||
|     "name": "setContextDirty" |     "name": "setContextDirty" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "setContextPlayersDirty" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "setCurrentInjector" |     "name": "setCurrentInjector" | ||||||
|   }, |   }, | ||||||
| @ -983,6 +1019,12 @@ | |||||||
|   { |   { | ||||||
|     "name": "setInputsFromAttrs" |     "name": "setInputsFromAttrs" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "setPlayerBuilder" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     "name": "setPlayerBuilderIndex" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "setProp" |     "name": "setProp" | ||||||
|   }, |   }, | ||||||
|  | |||||||
| @ -59,6 +59,9 @@ | |||||||
|   { |   { | ||||||
|     "name": "BROWSER_SANITIZATION_PROVIDERS" |     "name": "BROWSER_SANITIZATION_PROVIDERS" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "BoundPlayerFactory" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "BrowserDomAdapter" |     "name": "BrowserDomAdapter" | ||||||
|   }, |   }, | ||||||
| @ -128,6 +131,9 @@ | |||||||
|   { |   { | ||||||
|     "name": "ChangeDetectorRef" |     "name": "ChangeDetectorRef" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "ClassAndStylePlayerBuilder" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "CommonModule" |     "name": "CommonModule" | ||||||
|   }, |   }, | ||||||
| @ -164,6 +170,9 @@ | |||||||
|   { |   { | ||||||
|     "name": "Console" |     "name": "Console" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "CorePlayerHandler" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "CurrencyPipe" |     "name": "CurrencyPipe" | ||||||
|   }, |   }, | ||||||
| @ -1169,6 +1178,9 @@ | |||||||
|   { |   { | ||||||
|     "name": "addDateMinutes" |     "name": "addDateMinutes" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "addPlayerInternal" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "addRemoveViewFromContainer" |     "name": "addRemoveViewFromContainer" | ||||||
|   }, |   }, | ||||||
| @ -1178,6 +1190,9 @@ | |||||||
|   { |   { | ||||||
|     "name": "adjustBlueprintForNewNode" |     "name": "adjustBlueprintForNewNode" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "allocPlayerContext" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "allocStylingContext" |     "name": "allocStylingContext" | ||||||
|   }, |   }, | ||||||
| @ -1793,6 +1808,15 @@ | |||||||
|   { |   { | ||||||
|     "name": "getPlatform" |     "name": "getPlatform" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "getPlayerBuilder" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     "name": "getPlayerBuilderIndex" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     "name": "getPlayerContext" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "getPluralCategory" |     "name": "getPluralCategory" | ||||||
|   }, |   }, | ||||||
| @ -1823,6 +1847,12 @@ | |||||||
|   { |   { | ||||||
|     "name": "getRendererFactory" |     "name": "getRendererFactory" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "getRootContext" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     "name": "getRootView" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "getStyleSanitizer" |     "name": "getStyleSanitizer" | ||||||
|   }, |   }, | ||||||
| @ -1868,6 +1898,9 @@ | |||||||
|   { |   { | ||||||
|     "name": "hasOnDestroy" |     "name": "hasOnDestroy" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "hasPlayerBuilderChanged" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "hasValueChanged" |     "name": "hasValueChanged" | ||||||
|   }, |   }, | ||||||
| @ -2262,7 +2295,7 @@ | |||||||
|     "name": "renderEmbeddedTemplate" |     "name": "renderEmbeddedTemplate" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "name": "renderStyling" |     "name": "renderStyleAndClassBindings" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "name": "resetComponentState" |     "name": "resetComponentState" | ||||||
| @ -2315,6 +2348,9 @@ | |||||||
|   { |   { | ||||||
|     "name": "setContextDirty" |     "name": "setContextDirty" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "setContextPlayersDirty" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "setCurrentInjector" |     "name": "setCurrentInjector" | ||||||
|   }, |   }, | ||||||
| @ -2336,6 +2372,12 @@ | |||||||
|   { |   { | ||||||
|     "name": "setInputsFromAttrs" |     "name": "setInputsFromAttrs" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "name": "setPlayerBuilder" | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     "name": "setPlayerBuilderIndex" | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "name": "setProp" |     "name": "setProp" | ||||||
|   }, |   }, | ||||||
|  | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -14,6 +14,8 @@ export class MockPlayer implements Player { | |||||||
|   state: PlayState = PlayState.Pending; |   state: PlayState = PlayState.Pending; | ||||||
|   private _listeners: {[state: string]: (() => any)[]} = {}; |   private _listeners: {[state: string]: (() => any)[]} = {}; | ||||||
| 
 | 
 | ||||||
|  |   constructor(public value?: any) {} | ||||||
|  | 
 | ||||||
|   play(): void { |   play(): void { | ||||||
|     if (this.state === PlayState.Running) return; |     if (this.state === PlayState.Running) return; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -9,9 +9,9 @@ import {RenderFlags} from '@angular/core/src/render3'; | |||||||
| 
 | 
 | ||||||
| import {defineComponent, getHostElement} from '../../../src/render3/index'; | import {defineComponent, getHostElement} from '../../../src/render3/index'; | ||||||
| import {element, elementEnd, elementStart, elementStyling, elementStylingApply, load, markDirty} from '../../../src/render3/instructions'; | import {element, elementEnd, elementStart, elementStyling, elementStylingApply, load, markDirty} from '../../../src/render3/instructions'; | ||||||
| import {PlayState, Player, PlayerContext, PlayerHandler} from '../../../src/render3/interfaces/player'; | import {PlayState, Player, PlayerHandler} from '../../../src/render3/interfaces/player'; | ||||||
| import {RElement} from '../../../src/render3/interfaces/renderer'; | import {RElement} from '../../../src/render3/interfaces/renderer'; | ||||||
| import {addPlayer, getPlayers} from '../../../src/render3/player'; | import {addPlayer, getPlayers} from '../../../src/render3/players'; | ||||||
| import {QueryList, query, queryRefresh} from '../../../src/render3/query'; | import {QueryList, query, queryRefresh} from '../../../src/render3/query'; | ||||||
| import {getOrCreatePlayerContext} from '../../../src/render3/styling/util'; | import {getOrCreatePlayerContext} from '../../../src/render3/styling/util'; | ||||||
| import {ComponentFixture} from '../render_util'; | import {ComponentFixture} from '../render_util'; | ||||||
| @ -56,14 +56,14 @@ describe('animation player access', () => { | |||||||
|   it('should add a player to the element animation context and remove it once it completes', () => { |   it('should add a player to the element animation context and remove it once it completes', () => { | ||||||
|     const element = buildElement(); |     const element = buildElement(); | ||||||
|     const context = getOrCreatePlayerContext(element); |     const context = getOrCreatePlayerContext(element); | ||||||
|     expect(context).toEqual([]); |     expect(getPlayers(element)).toEqual([]); | ||||||
| 
 | 
 | ||||||
|     const player = new MockPlayer(); |     const player = new MockPlayer(); | ||||||
|     addPlayer(element, player); |     addPlayer(element, player); | ||||||
|     expect(readPlayers(context)).toEqual([player]); |     expect(getPlayers(element)).toEqual([player]); | ||||||
| 
 | 
 | ||||||
|     player.destroy(); |     player.destroy(); | ||||||
|     expect(readPlayers(context)).toEqual([]); |     expect(getPlayers(element)).toEqual([]); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   it('should flush all pending animation players after change detection', () => { |   it('should flush all pending animation players after change detection', () => { | ||||||
| @ -226,10 +226,6 @@ function buildElementWithStyling() { | |||||||
|   return fixture.hostElement.querySelector('div') as RElement; |   return fixture.hostElement.querySelector('div') as RElement; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function readPlayers(context: PlayerContext): Player[] { |  | ||||||
|   return context; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| class Comp { | class Comp { | ||||||
|   static ngComponentDef = defineComponent({ |   static ngComponentDef = defineComponent({ | ||||||
|     type: Comp, |     type: Comp, | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user