| /* | 
 | * Licensed to the Apache Software Foundation (ASF) under one | 
 | * or more contributor license agreements.  See the NOTICE file | 
 | * distributed with this work for additional information | 
 | * regarding copyright ownership.  The ASF licenses this file | 
 | * to you under the Apache License, Version 2.0 (the | 
 | * "License"); you may not use this file except in compliance | 
 | * with the License.  You may obtain a copy of the License at | 
 | * | 
 | *   http://www.apache.org/licenses/LICENSE-2.0 | 
 | * | 
 | * Unless required by applicable law or agreed to in writing, | 
 | * software distributed under the License is distributed on an | 
 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | 
 | * KIND, either express or implied.  See the License for the | 
 | * specific language governing permissions and limitations | 
 | * under the License. | 
 | */ | 
 |  | 
 | // Helpers for creating transitions in custom series and graphic components. | 
 | import Element, { ElementAnimateConfig, ElementProps } from 'zrender/src/Element'; | 
 |  | 
 | import { makeInner, normalizeToArray } from '../util/model'; | 
 | import { assert, bind, each, eqNaN, extend, hasOwn, indexOf, isArrayLike, keys, reduce } from 'zrender/src/core/util'; | 
 | import { cloneValue } from 'zrender/src/animation/Animator'; | 
 | import Displayable, { DisplayableProps } from 'zrender/src/graphic/Displayable'; | 
 | import Model from '../model/Model'; | 
 | import { getAnimationConfig } from './basicTransition'; | 
 | import { Path } from '../util/graphic'; | 
 | import { warn } from '../util/log'; | 
 | import { AnimationOption, AnimationOptionMixin, ZRStyleProps } from '../util/types'; | 
 | import { Dictionary } from 'zrender/src/core/types'; | 
 | import { PathStyleProps } from 'zrender/src/graphic/Path'; | 
 | import { TRANSFORMABLE_PROPS, TransformProp } from 'zrender/src/core/Transformable'; | 
 |  | 
 | const LEGACY_TRANSFORM_PROPS_MAP = { | 
 |     position: ['x', 'y'], | 
 |     scale: ['scaleX', 'scaleY'], | 
 |     origin: ['originX', 'originY'] | 
 | } as const; | 
 | const LEGACY_TRANSFORM_PROPS = keys(LEGACY_TRANSFORM_PROPS_MAP); | 
 |  | 
 | const TRANSFORM_PROPS_MAP = reduce(TRANSFORMABLE_PROPS, (obj, key) => { | 
 |     obj[key] = 1; | 
 |     return obj; | 
 | }, {} as Record<TransformProp, 1>); | 
 | const transformPropNamesStr = TRANSFORMABLE_PROPS.join(', '); | 
 |  | 
 | // '' means root | 
 | export const ELEMENT_ANIMATABLE_PROPS = ['', 'style', 'shape', 'extra'] as const; | 
 |  | 
 | export type TransitionProps = string | string[]; | 
 | export type ElementRootTransitionProp = TransformProp | 'shape' | 'extra' | 'style'; | 
 |  | 
 | export interface TransitionOptionMixin<T = Record<string, any>> { | 
 |     transition?: (keyof T & string) | ((keyof T & string)[]) | 'all' | 
 |  | 
 |     enterFrom?: T; | 
 |     leaveTo?: T; | 
 |  | 
 |     enterAnimation?: AnimationOption | 
 |     updateAnimation?: AnimationOption | 
 |     leaveAnimation?: AnimationOption | 
 | }; | 
 |  | 
 | interface LooseElementProps extends ElementProps { | 
 |     style?: ZRStyleProps; | 
 |     shape?: Dictionary<unknown>; | 
 | } | 
 |  | 
 | type TransitionElementOption = Partial<Record<TransformProp, number>> & { | 
 |     shape?: Dictionary<any> & TransitionOptionMixin | 
 |     style?: PathStyleProps & TransitionOptionMixin | 
 |     extra?: Dictionary<any> & TransitionOptionMixin | 
 |     invisible?: boolean | 
 |     silent?: boolean | 
 |     autoBatch?: boolean | 
 |     ignore?: boolean | 
 |  | 
 |     during?: (params: TransitionDuringAPI) => void | 
 | } & TransitionOptionMixin; | 
 |  | 
 | const transitionInnerStore = makeInner<{ | 
 |     leaveToProps: ElementProps; | 
 |     userDuring: (params: TransitionDuringAPI) => void; | 
 | }, Element>(); | 
 |  | 
 | export interface TransitionBaseDuringAPI { | 
 |     // Usually other props do not need to be changed in animation during. | 
 |     setTransform(key: TransformProp, val: number): this | 
 |     getTransform(key: TransformProp): number; | 
 |     setExtra(key: string, val: unknown): this | 
 |     getExtra(key: string): unknown | 
 | } | 
 | export interface TransitionDuringAPI< | 
 |     StyleOpt extends any = any, | 
 |     ShapeOpt extends any = any | 
 | > extends TransitionBaseDuringAPI { | 
 |     setShape<T extends keyof ShapeOpt>(key: T, val: ShapeOpt[T]): this; | 
 |     getShape<T extends keyof ShapeOpt>(key: T): ShapeOpt[T]; | 
 |     setStyle<T extends keyof StyleOpt>(key: T, val: StyleOpt[T]): this | 
 |     getStyle<T extends keyof StyleOpt>(key: T): StyleOpt[T]; | 
 | }; | 
 |  | 
 | function getElementAnimationConfig( | 
 |     animationType: 'enter' | 'update' | 'leave', | 
 |     el: Element, | 
 |     elOption: TransitionElementOption, | 
 |     parentModel: Model<AnimationOptionMixin>, | 
 |     dataIndex?: number | 
 | ) { | 
 |     const animationProp = `${animationType}Animation` as const; | 
 |     const config: ElementAnimateConfig = getAnimationConfig(animationType, parentModel, dataIndex) || {}; | 
 |  | 
 |     const userDuring = transitionInnerStore(el).userDuring; | 
 |     // Only set when duration is > 0 and it's need to be animated. | 
 |     if (config.duration > 0) { | 
 |         // For simplicity, if during not specified, the previous during will not work any more. | 
 |         config.during = userDuring ? bind(duringCall, { el: el, userDuring: userDuring }) : null; | 
 |         config.setToFinal = true; | 
 |         config.scope = animationType; | 
 |     } | 
 |  | 
 |     extend(config, elOption[animationProp]); | 
 |     return config; | 
 | } | 
 |  | 
 |  | 
 | export function applyUpdateTransition( | 
 |     el: Element, | 
 |     elOption: TransitionElementOption, | 
 |     animatableModel?: Model<AnimationOptionMixin>, | 
 |     opts?: { | 
 |         dataIndex?: number, | 
 |         isInit?: boolean, | 
 |         clearStyle?: boolean | 
 |     } | 
 | ) { | 
 |     opts = opts || {}; | 
 |     const {dataIndex, isInit, clearStyle} = opts; | 
 |  | 
 |     const hasAnimation = animatableModel.isAnimationEnabled(); | 
 |     // Save the meta info for further morphing. Like apply on the sub morphing elements. | 
 |     const store = transitionInnerStore(el); | 
 |     const styleOpt = elOption.style; | 
 |     store.userDuring = elOption.during; | 
 |  | 
 |     const transFromProps = {} as ElementProps; | 
 |     const propsToSet = {} as ElementProps; | 
 |  | 
 |     prepareTransformAllPropsFinal(el, elOption, propsToSet); | 
 |     prepareShapeOrExtraAllPropsFinal('shape', elOption, propsToSet); | 
 |     prepareShapeOrExtraAllPropsFinal('extra', elOption, propsToSet); | 
 |  | 
 |     if (!isInit && hasAnimation) { | 
 |         prepareTransformTransitionFrom(el, elOption, transFromProps); | 
 |         prepareShapeOrExtraTransitionFrom('shape', el, elOption, transFromProps); | 
 |         prepareShapeOrExtraTransitionFrom('extra', el, elOption, transFromProps); | 
 |         prepareStyleTransitionFrom(el, elOption, styleOpt, transFromProps); | 
 |     } | 
 |  | 
 |     (propsToSet as DisplayableProps).style = styleOpt; | 
 |  | 
 |     applyPropsDirectly(el, propsToSet, clearStyle); | 
 |     applyMiscProps(el, elOption); | 
 |  | 
 |     if (hasAnimation) { | 
 |         if (isInit) { | 
 |             const enterFromProps: ElementProps = {}; | 
 |             each(ELEMENT_ANIMATABLE_PROPS, propName => { | 
 |                 const prop: TransitionOptionMixin = propName ? elOption[propName] : elOption; | 
 |                 if (prop && prop.enterFrom) { | 
 |                     if (propName) { | 
 |                         (enterFromProps as any)[propName] = (enterFromProps as any)[propName] || {}; | 
 |                     } | 
 |                     extend(propName ? (enterFromProps as any)[propName] : enterFromProps, prop.enterFrom); | 
 |                 } | 
 |             }); | 
 |             const config = getElementAnimationConfig('enter', el, elOption, animatableModel, dataIndex); | 
 |             if (config.duration > 0) { | 
 |                 el.animateFrom(enterFromProps, config); | 
 |             } | 
 |         } | 
 |         else { | 
 |             applyPropsTransition(el, elOption, dataIndex || 0, animatableModel, transFromProps); | 
 |         } | 
 |     } | 
 |     // Store leave to be used in leave transition. | 
 |     updateLeaveTo(el, elOption); | 
 |  | 
 |     styleOpt ? el.dirty() : el.markRedraw(); | 
 | } | 
 |  | 
 | export function updateLeaveTo(el: Element, elOption: TransitionElementOption) { | 
 |     // Try merge to previous set leaveTo | 
 |     let leaveToProps: ElementProps = transitionInnerStore(el).leaveToProps; | 
 |     for (let i = 0; i < ELEMENT_ANIMATABLE_PROPS.length; i++) { | 
 |         const propName = ELEMENT_ANIMATABLE_PROPS[i]; | 
 |         const prop: TransitionOptionMixin = propName ? elOption[propName] : elOption; | 
 |         if (prop && prop.leaveTo) { | 
 |             if (!leaveToProps) { | 
 |                 leaveToProps = transitionInnerStore(el).leaveToProps = {}; | 
 |             } | 
 |             if (propName) { | 
 |                 (leaveToProps as any)[propName] = (leaveToProps as any)[propName] || {}; | 
 |             } | 
 |             extend(propName ? (leaveToProps as any)[propName] : leaveToProps, prop.leaveTo); | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | export function applyLeaveTransition( | 
 |     el: Element, | 
 |     elOption: TransitionElementOption, | 
 |     animatableModel: Model<AnimationOptionMixin>, | 
 |     onRemove?: () => void | 
 | ): void { | 
 |     if (el) { | 
 |         const parent = el.parent; | 
 |         const leaveToProps = transitionInnerStore(el).leaveToProps; | 
 |         if (leaveToProps) { | 
 |             // TODO TODO use leave after leaveAnimation in series is introduced | 
 |             // TODO Data index? | 
 |             const config = getElementAnimationConfig('update', el, elOption, animatableModel, 0); | 
 |             config.done = () => { | 
 |                 parent.remove(el); | 
 |                 onRemove && onRemove(); | 
 |             }; | 
 |             el.animateTo(leaveToProps, config); | 
 |         } | 
 |         else { | 
 |             parent.remove(el); | 
 |             onRemove && onRemove(); | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | export function isTransitionAll(transition: TransitionProps): transition is 'all' { | 
 |     return transition === 'all'; | 
 | } | 
 |  | 
 |  | 
 | function applyPropsDirectly( | 
 |     el: Element, | 
 |     // Can be null/undefined | 
 |     allPropsFinal: ElementProps, | 
 |     clearStyle: boolean | 
 | ) { | 
 |     const styleOpt = (allPropsFinal as Displayable).style; | 
 |     if (!el.isGroup && styleOpt) { | 
 |         if (clearStyle) { | 
 |             (el as Displayable).useStyle({}); | 
 |  | 
 |             // When style object changed, how to trade the existing animation? | 
 |             // It is probably complicated and not needed to cover all the cases. | 
 |             // But still need consider the case: | 
 |             // (1) When using init animation on `style.opacity`, and before the animation | 
 |             //     ended users triggers an update by mousewhel. At that time the init | 
 |             //     animation should better be continued rather than terminated. | 
 |             //     So after `useStyle` called, we should change the animation target manually | 
 |             //     to continue the effect of the init animation. | 
 |             // (2) PENDING: If the previous animation targeted at a `val1`, and currently we need | 
 |             //     to update the value to `val2` and no animation declared, should be terminate | 
 |             //     the previous animation or just modify the target of the animation? | 
 |             //     Therotically That will happen not only on `style` but also on `shape` and | 
 |             //     `transfrom` props. But we haven't handle this case at present yet. | 
 |             // (3) PENDING: Is it proper to visit `animators` and `targetName`? | 
 |             const animators = el.animators; | 
 |             for (let i = 0; i < animators.length; i++) { | 
 |                 const animator = animators[i]; | 
 |                 // targetName is the "topKey". | 
 |                 if (animator.targetName === 'style') { | 
 |                     animator.changeTarget((el as Displayable).style); | 
 |                 } | 
 |             } | 
 |         } | 
 |         (el as Displayable).setStyle(styleOpt); | 
 |     } | 
 |  | 
 |     if (allPropsFinal) { | 
 |         // Not set style here. | 
 |         (allPropsFinal as DisplayableProps).style = null; | 
 |         // Set el to the final state firstly. | 
 |         allPropsFinal && el.attr(allPropsFinal); | 
 |         (allPropsFinal as DisplayableProps).style = styleOpt; | 
 |     } | 
 | } | 
 |  | 
 | function applyPropsTransition( | 
 |     el: Element, | 
 |     elOption: TransitionElementOption, | 
 |     dataIndex: number, | 
 |     model: Model<AnimationOptionMixin>, | 
 |     // Can be null/undefined | 
 |     transFromProps: ElementProps | 
 | ): void { | 
 |     if (transFromProps) { | 
 |         const config = getElementAnimationConfig('update', el, elOption, model, dataIndex); | 
 |         if (config.duration > 0) { | 
 |             el.animateFrom(transFromProps, config); | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 |  | 
 | function applyMiscProps( | 
 |     el: Element, | 
 |     elOption: TransitionElementOption | 
 | ) { | 
 |     // Merge by default. | 
 |     hasOwn(elOption, 'silent') && (el.silent = elOption.silent); | 
 |     hasOwn(elOption, 'ignore') && (el.ignore = elOption.ignore); | 
 |     if (el instanceof Displayable) { | 
 |         hasOwn(elOption, 'invisible') && ((el as Path).invisible = elOption.invisible); | 
 |     } | 
 |     if (el instanceof Path) { | 
 |         hasOwn(elOption, 'autoBatch') && ((el as Path).autoBatch = elOption.autoBatch); | 
 |     } | 
 | } | 
 |  | 
 | // Use it to avoid it be exposed to user. | 
 | const tmpDuringScope = {} as { | 
 |     el: Element; | 
 | }; | 
 | const transitionDuringAPI: TransitionDuringAPI = { | 
 |     // Usually other props do not need to be changed in animation during. | 
 |     setTransform(key: TransformProp, val: unknown) { | 
 |         if (__DEV__) { | 
 |             assert(hasOwn(TRANSFORM_PROPS_MAP, key), 'Only ' + transformPropNamesStr + ' available in `setTransform`.'); | 
 |         } | 
 |         tmpDuringScope.el[key] = val as number; | 
 |         return this; | 
 |     }, | 
 |     getTransform(key: TransformProp): number { | 
 |         if (__DEV__) { | 
 |             assert(hasOwn(TRANSFORM_PROPS_MAP, key), 'Only ' + transformPropNamesStr + ' available in `getTransform`.'); | 
 |         } | 
 |         return tmpDuringScope.el[key]; | 
 |     }, | 
 |     setShape(key: any, val: unknown) { | 
 |         if (__DEV__) { | 
 |             assertNotReserved(key); | 
 |         } | 
 |         const el = tmpDuringScope.el as Path; | 
 |         const shape = el.shape || (el.shape = {}); | 
 |         shape[key] = val; | 
 |         el.dirtyShape && el.dirtyShape(); | 
 |         return this; | 
 |     }, | 
 |     getShape(key: any): any { | 
 |         if (__DEV__) { | 
 |             assertNotReserved(key); | 
 |         } | 
 |         const shape = (tmpDuringScope.el as Path).shape; | 
 |         if (shape) { | 
 |             return shape[key]; | 
 |         } | 
 |     }, | 
 |     setStyle(key: any, val: unknown) { | 
 |         if (__DEV__) { | 
 |             assertNotReserved(key); | 
 |         } | 
 |         const el = tmpDuringScope.el as Displayable; | 
 |         const style = el.style; | 
 |         if (style) { | 
 |             if (__DEV__) { | 
 |                 if (eqNaN(val)) { | 
 |                     warn('style.' + key + ' must not be assigned with NaN.'); | 
 |                 } | 
 |             } | 
 |             style[key] = val; | 
 |             el.dirtyStyle && el.dirtyStyle(); | 
 |         } | 
 |         return this; | 
 |     }, | 
 |     getStyle(key: any): any { | 
 |         if (__DEV__) { | 
 |             assertNotReserved(key); | 
 |         } | 
 |         const style = (tmpDuringScope.el as Displayable).style; | 
 |         if (style) { | 
 |             return style[key]; | 
 |         } | 
 |     }, | 
 |     setExtra(key: any, val: unknown) { | 
 |         if (__DEV__) { | 
 |             assertNotReserved(key); | 
 |         } | 
 |         const extra = (tmpDuringScope.el as LooseElementProps).extra | 
 |             || ((tmpDuringScope.el as LooseElementProps).extra = {}); | 
 |         extra[key] = val; | 
 |         return this; | 
 |     }, | 
 |     getExtra(key: string): unknown { | 
 |         if (__DEV__) { | 
 |             assertNotReserved(key); | 
 |         } | 
 |         const extra = (tmpDuringScope.el as LooseElementProps).extra; | 
 |         if (extra) { | 
 |             return extra[key]; | 
 |         } | 
 |     } | 
 | }; | 
 |  | 
 | function assertNotReserved(key: string) { | 
 |     if (__DEV__) { | 
 |         if (key === 'transition' || key === 'enterFrom' || key === 'leaveTo') { | 
 |             throw new Error('key must not be "' + key + '"'); | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | function duringCall( | 
 |     this: { | 
 |         el: Element; | 
 |         userDuring: (params: TransitionDuringAPI) => void; | 
 |     } | 
 | ): void { | 
 |     // Do not provide "percent" until some requirements come. | 
 |     // Because consider thies case: | 
 |     // enterFrom: {x: 100, y: 30}, transition: 'x'. | 
 |     // And enter duration is different from update duration. | 
 |     // Thus it might be confused about the meaning of "percent" in during callback. | 
 |     const scope = this; | 
 |     const el = scope.el; | 
 |     if (!el) { | 
 |         return; | 
 |     } | 
 |     // If el is remove from zr by reason like legend, during still need to called, | 
 |     // because el will be added back to zr and the prop value should not be incorrect. | 
 |  | 
 |     const latestUserDuring = transitionInnerStore(el).userDuring; | 
 |     const scopeUserDuring = scope.userDuring; | 
 |     // Ensured a during is only called once in each animation frame. | 
 |     // If a during is called multiple times in one frame, maybe some users' calculation logic | 
 |     // might be wrong (not sure whether this usage exists). | 
 |     // The case of a during might be called twice can be: by default there is a animator for | 
 |     // 'x', 'y' when init. Before the init animation finished, call `setOption` to start | 
 |     // another animators for 'style'/'shape'/'extra'. | 
 |     if (latestUserDuring !== scopeUserDuring) { | 
 |         // release | 
 |         scope.el = scope.userDuring = null; | 
 |         return; | 
 |     } | 
 |  | 
 |     tmpDuringScope.el = el; | 
 |  | 
 |     // Give no `this` to user in "during" calling. | 
 |     scopeUserDuring(transitionDuringAPI); | 
 |  | 
 |     // FIXME: if in future meet the case that some prop will be both modified in `during` and `state`, | 
 |     // consider the issue that the prop might be incorrect when return to "normal" state. | 
 | } | 
 |  | 
 | function prepareShapeOrExtraTransitionFrom( | 
 |     mainAttr: 'shape' | 'extra', | 
 |     fromEl: Element, | 
 |     elOption: TransitionOptionMixin, | 
 |     transFromProps: LooseElementProps | 
 | ): void { | 
 |  | 
 |     const attrOpt: Dictionary<unknown> & TransitionOptionMixin = (elOption as any)[mainAttr]; | 
 |     if (!attrOpt) { | 
 |         return; | 
 |     } | 
 |  | 
 |     const elPropsInAttr = (fromEl as LooseElementProps)[mainAttr]; | 
 |     let transFromPropsInAttr: Dictionary<unknown>; | 
 |  | 
 |     if (elPropsInAttr) { | 
 |         const transition = elOption.transition; | 
 |         const attrTransition = attrOpt.transition; | 
 |         if (attrTransition) { | 
 |             !transFromPropsInAttr && (transFromPropsInAttr = transFromProps[mainAttr] = {}); | 
 |             if (isTransitionAll(attrTransition)) { | 
 |                 extend(transFromPropsInAttr, elPropsInAttr); | 
 |             } | 
 |             else { | 
 |                 const transitionKeys = normalizeToArray(attrTransition); | 
 |                 for (let i = 0; i < transitionKeys.length; i++) { | 
 |                     const key = transitionKeys[i]; | 
 |                     const elVal = elPropsInAttr[key]; | 
 |                     transFromPropsInAttr[key] = elVal; | 
 |                 } | 
 |             } | 
 |         } | 
 |         else if (isTransitionAll(transition) || indexOf(transition, mainAttr) >= 0) { | 
 |             !transFromPropsInAttr && (transFromPropsInAttr = transFromProps[mainAttr] = {}); | 
 |             const elPropsInAttrKeys = keys(elPropsInAttr); | 
 |             for (let i = 0; i < elPropsInAttrKeys.length; i++) { | 
 |                 const key = elPropsInAttrKeys[i]; | 
 |                 const elVal = elPropsInAttr[key]; | 
 |                 if (isNonStyleTransitionEnabled((attrOpt as any)[key], elVal)) { | 
 |                     transFromPropsInAttr[key] = elVal; | 
 |                 } | 
 |             } | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | function prepareShapeOrExtraAllPropsFinal( | 
 |     mainAttr: 'shape' | 'extra', | 
 |     elOption: TransitionElementOption, | 
 |     allProps: LooseElementProps | 
 | ): void { | 
 |     const attrOpt: Dictionary<unknown> = (elOption as any)[mainAttr]; | 
 |     if (!attrOpt) { | 
 |         return; | 
 |     } | 
 |     const allPropsInAttr = allProps[mainAttr] = {} as Dictionary<unknown>; | 
 |     const keysInAttr = keys(attrOpt); | 
 |     for (let i = 0; i < keysInAttr.length; i++) { | 
 |         const key = keysInAttr[i]; | 
 |         // To avoid share one object with different element, and | 
 |         // to avoid user modify the object inexpectedly, have to clone. | 
 |         allPropsInAttr[key] = cloneValue((attrOpt as any)[key]); | 
 |     } | 
 | } | 
 |  | 
 | function prepareTransformTransitionFrom( | 
 |     el: Element, | 
 |     elOption: TransitionElementOption, | 
 |     transFromProps: ElementProps | 
 | ): void { | 
 |     const transition = elOption.transition; | 
 |     const transitionKeys = isTransitionAll(transition) | 
 |         ? TRANSFORMABLE_PROPS | 
 |         : normalizeToArray(transition || []); | 
 |     for (let i = 0; i < transitionKeys.length; i++) { | 
 |         const key = transitionKeys[i]; | 
 |         if (key === 'style' || key === 'shape' || key === 'extra') { | 
 |             continue; | 
 |         } | 
 |         const elVal = (el as any)[key]; | 
 |         if (__DEV__) { | 
 |             checkTransformPropRefer(key, 'el.transition'); | 
 |         } | 
 |         // Do not clone, animator will perform that clone. | 
 |         (transFromProps as any)[key] = elVal; | 
 |     } | 
 | } | 
 |  | 
 | function prepareTransformAllPropsFinal( | 
 |     el: Element, | 
 |     elOption: TransitionElementOption, | 
 |     allProps: ElementProps | 
 | ): void { | 
 |     for (let i = 0; i < LEGACY_TRANSFORM_PROPS.length; i++) { | 
 |         const legacyName = LEGACY_TRANSFORM_PROPS[i]; | 
 |         const xyName = LEGACY_TRANSFORM_PROPS_MAP[legacyName]; | 
 |         const legacyArr = (elOption as any)[legacyName]; | 
 |         if (legacyArr) { | 
 |             allProps[xyName[0]] = legacyArr[0]; | 
 |             allProps[xyName[1]] = legacyArr[1]; | 
 |         } | 
 |     } | 
 |  | 
 |     for (let i = 0; i < TRANSFORMABLE_PROPS.length; i++) { | 
 |         const key = TRANSFORMABLE_PROPS[i]; | 
 |         if (elOption[key] != null) { | 
 |             allProps[key] = elOption[key]; | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | function prepareStyleTransitionFrom( | 
 |     fromEl: Element, | 
 |     elOption: TransitionElementOption, | 
 |     styleOpt: TransitionElementOption['style'], | 
 |     transFromProps: LooseElementProps | 
 | ): void { | 
 |     if (!styleOpt) { | 
 |         return; | 
 |     } | 
 |  | 
 |     const fromElStyle = (fromEl as LooseElementProps).style as LooseElementProps['style']; | 
 |     let transFromStyleProps: LooseElementProps['style']; | 
 |  | 
 |     if (fromElStyle) { | 
 |         const styleTransition = styleOpt.transition; | 
 |         const elTransition = elOption.transition; | 
 |         if (styleTransition && !isTransitionAll(styleTransition)) { | 
 |             const transitionKeys = normalizeToArray(styleTransition); | 
 |             !transFromStyleProps && (transFromStyleProps = transFromProps.style = {}); | 
 |             for (let i = 0; i < transitionKeys.length; i++) { | 
 |                 const key = transitionKeys[i]; | 
 |                 const elVal = (fromElStyle as any)[key]; | 
 |                 // Do not clone, see `checkNonStyleTansitionRefer`. | 
 |                 (transFromStyleProps as any)[key] = elVal; | 
 |             } | 
 |         } | 
 |         else if ( | 
 |             (fromEl as Displayable).getAnimationStyleProps | 
 |             && ( | 
 |                 isTransitionAll(elTransition) | 
 |                 || isTransitionAll(styleTransition) | 
 |                 || indexOf(elTransition, 'style') >= 0 | 
 |             ) | 
 |         ) { | 
 |             const animationProps = (fromEl as Displayable).getAnimationStyleProps(); | 
 |             const animationStyleProps = animationProps ? animationProps.style : null; | 
 |             if (animationStyleProps) { | 
 |                 !transFromStyleProps && (transFromStyleProps = transFromProps.style = {}); | 
 |                 const styleKeys = keys(styleOpt); | 
 |                 for (let i = 0; i < styleKeys.length; i++) { | 
 |                     const key = styleKeys[i]; | 
 |                     if ((animationStyleProps as Dictionary<unknown>)[key]) { | 
 |                         const elVal = (fromElStyle as any)[key]; | 
 |                         (transFromStyleProps as any)[key] = elVal; | 
 |                     } | 
 |                 } | 
 |             } | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | function isNonStyleTransitionEnabled(optVal: unknown, elVal: unknown): boolean { | 
 |     // The same as `checkNonStyleTansitionRefer`. | 
 |     return !isArrayLike(optVal) | 
 |         ? (optVal != null && isFinite(optVal as number)) | 
 |         : optVal !== elVal; | 
 | } | 
 |  | 
 | let checkTransformPropRefer: (key: string, usedIn: string) => void; | 
 | if (__DEV__) { | 
 |     checkTransformPropRefer = function (key: string, usedIn: string): void { | 
 |         if (!hasOwn(TRANSFORM_PROPS_MAP, key)) { | 
 |             warn('Prop `' + key + '` is not a permitted in `' + usedIn + '`. ' | 
 |                + 'Only `' + keys(TRANSFORM_PROPS_MAP).join('`, `') + '` are permitted.'); | 
 |         } | 
 |     }; | 
 | } |