| /* | |
| * 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.'); | |
| } | |
| }; | |
| } |