/** * @license * Copyright Google LLC 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 { AnimationEvent, AnimationPlayer, AUTO_STYLE, NoopAnimationPlayer, ɵAnimationGroupPlayer, ɵPRE_STYLE as PRE_STYLE, ɵStyleDataMap, } from '@angular/animations'; import {AnimationStyleNormalizer} from '../../src/dsl/style_normalization/animation_style_normalizer'; import {animationFailed} from '../error_helpers'; import {ANIMATABLE_PROP_SET} from './web_animations/animatable_props_set'; export function optimizeGroupPlayer(players: AnimationPlayer[]): AnimationPlayer { switch (players.length) { case 0: return new NoopAnimationPlayer(); case 1: return players[0]; default: return new ɵAnimationGroupPlayer(players); } } export function normalizeKeyframes( normalizer: AnimationStyleNormalizer, keyframes: Array<ɵStyleDataMap>, preStyles: ɵStyleDataMap = new Map(), postStyles: ɵStyleDataMap = new Map(), ): Array<ɵStyleDataMap> { const errors: Error[] = []; const normalizedKeyframes: Array<ɵStyleDataMap> = []; let previousOffset = -1; let previousKeyframe: ɵStyleDataMap | null = null; keyframes.forEach((kf) => { const offset = kf.get('offset') as number; const isSameOffset = offset == previousOffset; const normalizedKeyframe: ɵStyleDataMap = (isSameOffset && previousKeyframe) || new Map(); kf.forEach((val, prop) => { let normalizedProp = prop; let normalizedValue = val; if (prop !== 'offset') { normalizedProp = normalizer.normalizePropertyName(normalizedProp, errors); switch (normalizedValue) { case PRE_STYLE: normalizedValue = preStyles.get(prop)!; break; case AUTO_STYLE: normalizedValue = postStyles.get(prop)!; break; default: normalizedValue = normalizer.normalizeStyleValue( prop, normalizedProp, normalizedValue, errors, ); break; } } normalizedKeyframe.set(normalizedProp, normalizedValue); }); if (!isSameOffset) { normalizedKeyframes.push(normalizedKeyframe); } previousKeyframe = normalizedKeyframe; previousOffset = offset; }); if (errors.length) { throw animationFailed(errors); } return normalizedKeyframes; } export function listenOnPlayer( player: AnimationPlayer, eventName: string, event: AnimationEvent | undefined, callback: (event: any) => any, ) { switch (eventName) { case 'start': player.onStart(() => callback(event && copyAnimationEvent(event, 'start', player))); break; case 'done': player.onDone(() => callback(event && copyAnimationEvent(event, 'done', player))); break; case 'destroy': player.onDestroy(() => callback(event && copyAnimationEvent(event, 'destroy', player))); break; } } export function copyAnimationEvent( e: AnimationEvent, phaseName: string, player: AnimationPlayer, ): AnimationEvent { const totalTime = player.totalTime; const disabled = (player as any).disabled ? true : false; const event = makeAnimationEvent( e.element, e.triggerName, e.fromState, e.toState, phaseName || e.phaseName, totalTime == undefined ? e.totalTime : totalTime, disabled, ); const data = (e as any)['_data']; if (data != null) { (event as any)['_data'] = data; } return event; } export function makeAnimationEvent( element: any, triggerName: string, fromState: string, toState: string, phaseName: string = '', totalTime: number = 0, disabled?: boolean, ): AnimationEvent { return {element, triggerName, fromState, toState, phaseName, totalTime, disabled: !!disabled}; } export function getOrSetDefaultValue(map: Map, key: T, defaultValue: V) { let value = map.get(key); if (!value) { map.set(key, (value = defaultValue)); } return value; } export function parseTimelineCommand(command: string): [string, string] { const separatorPos = command.indexOf(':'); const id = command.substring(1, separatorPos); const action = command.slice(separatorPos + 1); return [id, action]; } const documentElement: HTMLElement | null = /* @__PURE__ */ (() => typeof document === 'undefined' ? null : document.documentElement)(); export function getParentElement(element: any): unknown | null { const parent = element.parentNode || element.host || null; // consider host to support shadow DOM if (parent === documentElement) { return null; } return parent; } function containsVendorPrefix(prop: string): boolean { // Webkit is the only real popular vendor prefix nowadays // cc: http://shouldiprefix.com/ return prop.substring(1, 6) == 'ebkit'; // webkit or Webkit } let _CACHED_BODY: {style: any} | null = null; let _IS_WEBKIT = false; export function validateStyleProperty(prop: string): boolean { if (!_CACHED_BODY) { _CACHED_BODY = getBodyNode() || {}; _IS_WEBKIT = _CACHED_BODY!.style ? 'WebkitAppearance' in _CACHED_BODY!.style : false; } let result = true; if (_CACHED_BODY!.style && !containsVendorPrefix(prop)) { result = prop in _CACHED_BODY!.style; if (!result && _IS_WEBKIT) { const camelProp = 'Webkit' + prop.charAt(0).toUpperCase() + prop.slice(1); result = camelProp in _CACHED_BODY!.style; } } return result; } export function validateWebAnimatableStyleProperty(prop: string): boolean { return ANIMATABLE_PROP_SET.has(prop); } export function getBodyNode(): any | null { if (typeof document != 'undefined') { return document.body; } return null; } export function containsElement(elm1: any, elm2: any): boolean { while (elm2) { if (elm2 === elm1) { return true; } elm2 = getParentElement(elm2); } return false; } export function invokeQuery(element: any, selector: string, multi: boolean): any[] { if (multi) { return Array.from(element.querySelectorAll(selector)); } const elem = element.querySelector(selector); return elem ? [elem] : []; } export function hypenatePropsKeys(original: ɵStyleDataMap): ɵStyleDataMap { const newMap: ɵStyleDataMap = new Map(); original.forEach((val, prop) => { const newProp = prop.replace(/([a-z])([A-Z])/g, '$1-$2'); newMap.set(newProp, val); }); return newMap; }