blob: 37f8d99e30f003c7adc569152fe14674099f60a6 [file] [log] [blame]
/*
* 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.
*/
import Transformable from 'zrender/src/core/Transformable';
import Element, { ElementProps } from 'zrender/src/Element';
import { Dictionary } from '../../util/types';
import {
CustomDisplayableOption,
CustomElementOption,
customInnerStore,
LooseElementProps,
TransformProp,
TRANSFORM_PROPS,
TransitionAnyOption
} from './CustomSeries';
import { normalizeToArray } from '../../util/model';
import { assert, hasOwn, indexOf, isArrayLike, keys } from 'zrender/src/core/util';
import { cloneValue } from 'zrender/src/animation/Animator';
import Displayable from 'zrender/src/graphic/Displayable';
const LEGACY_TRANSFORM_PROPS = {
position: ['x', 'y'],
scale: ['scaleX', 'scaleY'],
origin: ['originX', 'originY']
} as const;
type LegacyTransformProp = keyof typeof LEGACY_TRANSFORM_PROPS;
function setLegacyTransformProp(
elOption: CustomElementOption,
targetProps: Partial<Pick<Transformable, TransformProp>>,
legacyName: LegacyTransformProp
): void {
const legacyArr = (elOption as any)[legacyName];
const xyName = LEGACY_TRANSFORM_PROPS[legacyName];
if (legacyArr) {
targetProps[xyName[0]] = legacyArr[0];
targetProps[xyName[1]] = legacyArr[1];
}
}
function setTransformProp(
elOption: CustomElementOption,
allProps: Partial<Pick<Transformable, TransformProp>>,
name: TransformProp
): void {
if (elOption[name] != null) {
allProps[name] = elOption[name];
}
}
function setTransformPropToTransitionFrom(
transitionFrom: Partial<Pick<Transformable, TransformProp>>,
name: TransformProp,
fromTransformable?: Transformable // If provided, retrieve from the element.
): void {
if (fromTransformable) {
transitionFrom[name] = fromTransformable[name];
}
}
// See [STRATEGY_TRANSITION]
export function prepareShapeOrExtraTransitionFrom(
mainAttr: 'shape' | 'extra',
fromEl: Element,
elOption: CustomElementOption,
transFromProps: LooseElementProps,
isInit: boolean
): void {
const attrOpt: Dictionary<unknown> & TransitionAnyOption = (elOption as any)[mainAttr];
if (!attrOpt) {
return;
}
const elPropsInAttr = (fromEl as LooseElementProps)[mainAttr];
let transFromPropsInAttr: Dictionary<unknown>;
const enterFrom = attrOpt.enterFrom;
if (isInit && enterFrom) {
!transFromPropsInAttr && (transFromPropsInAttr = transFromProps[mainAttr] = {});
const enterFromKeys = keys(enterFrom);
for (let i = 0; i < enterFromKeys.length; i++) {
// `enterFrom` props are not necessarily also declared in `shape`/`style`/...,
// for example, `opacity` can only declared in `enterFrom` but not in `style`.
const key = enterFromKeys[i];
// Do not clone, animator will perform that clone.
transFromPropsInAttr[key] = enterFrom[key];
}
}
if (!isInit && elPropsInAttr) {
if (attrOpt.transition) {
!transFromPropsInAttr && (transFromPropsInAttr = transFromProps[mainAttr] = {});
const transitionKeys = normalizeToArray(attrOpt.transition);
for (let i = 0; i < transitionKeys.length; i++) {
const key = transitionKeys[i];
const elVal = elPropsInAttr[key];
if (__DEV__) {
checkNonStyleTansitionRefer(key, (attrOpt as any)[key], elVal);
}
// Do not clone, see `checkNonStyleTansitionRefer`.
transFromPropsInAttr[key] = elVal;
}
}
else if (indexOf(elOption.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;
}
}
}
}
const leaveTo = attrOpt.leaveTo;
if (leaveTo) {
const leaveToProps = getOrCreateLeaveToPropsFromEl(fromEl);
const leaveToPropsInAttr: Dictionary<unknown> = leaveToProps[mainAttr] || (leaveToProps[mainAttr] = {});
const leaveToKeys = keys(leaveTo);
for (let i = 0; i < leaveToKeys.length; i++) {
const key = leaveToKeys[i];
leaveToPropsInAttr[key] = leaveTo[key];
}
}
}
export function prepareShapeOrExtraAllPropsFinal(
mainAttr: 'shape' | 'extra',
elOption: CustomElementOption,
allProps: LooseElementProps
): void {
const attrOpt: Dictionary<unknown> & TransitionAnyOption = (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]);
}
}
// See [STRATEGY_TRANSITION].
export function prepareTransformTransitionFrom(
el: Element,
elOption: CustomElementOption,
transFromProps: ElementProps,
isInit: boolean
): void {
const enterFrom = elOption.enterFrom;
if (isInit && enterFrom) {
const enterFromKeys = keys(enterFrom);
for (let i = 0; i < enterFromKeys.length; i++) {
const key = enterFromKeys[i] as TransformProp;
if (__DEV__) {
checkTransformPropRefer(key, 'el.enterFrom');
}
// Do not clone, animator will perform that clone.
transFromProps[key] = enterFrom[key] as number;
}
}
if (!isInit) {
if (elOption.transition) {
const transitionKeys = normalizeToArray(elOption.transition);
for (let i = 0; i < transitionKeys.length; i++) {
const key = transitionKeys[i];
if (key === 'style' || key === 'shape' || key === 'extra') {
continue;
}
const elVal = el[key];
if (__DEV__) {
checkTransformPropRefer(key, 'el.transition');
checkNonStyleTansitionRefer(key, elOption[key], elVal);
}
// Do not clone, see `checkNonStyleTansitionRefer`.
transFromProps[key] = elVal;
}
}
// This default transition see [STRATEGY_TRANSITION]
else {
setTransformPropToTransitionFrom(transFromProps, 'x', el);
setTransformPropToTransitionFrom(transFromProps, 'y', el);
}
}
const leaveTo = elOption.leaveTo;
if (leaveTo) {
const leaveToProps = getOrCreateLeaveToPropsFromEl(el);
const leaveToKeys = keys(leaveTo);
for (let i = 0; i < leaveToKeys.length; i++) {
const key = leaveToKeys[i] as TransformProp;
if (__DEV__) {
checkTransformPropRefer(key, 'el.leaveTo');
}
leaveToProps[key] = leaveTo[key] as number;
}
}
}
export function prepareTransformAllPropsFinal(
el: Element,
elOption: CustomElementOption,
allProps: ElementProps
): void {
setLegacyTransformProp(elOption, allProps, 'position');
setLegacyTransformProp(elOption, allProps, 'scale');
setLegacyTransformProp(elOption, allProps, 'origin');
setTransformProp(elOption, allProps, 'x');
setTransformProp(elOption, allProps, 'y');
setTransformProp(elOption, allProps, 'scaleX');
setTransformProp(elOption, allProps, 'scaleY');
setTransformProp(elOption, allProps, 'originX');
setTransformProp(elOption, allProps, 'originY');
setTransformProp(elOption, allProps, 'rotation');
}
// See [STRATEGY_TRANSITION].
export function prepareStyleTransitionFrom(
fromEl: Element,
elOption: CustomElementOption,
styleOpt: CustomDisplayableOption['style'],
transFromProps: LooseElementProps,
isInit: boolean
): void {
if (!styleOpt) {
return;
}
const fromElStyle = (fromEl as LooseElementProps).style as LooseElementProps['style'];
let transFromStyleProps: LooseElementProps['style'];
const enterFrom = styleOpt.enterFrom;
if (isInit && enterFrom) {
const enterFromKeys = keys(enterFrom);
!transFromStyleProps && (transFromStyleProps = transFromProps.style = {});
for (let i = 0; i < enterFromKeys.length; i++) {
const key = enterFromKeys[i];
// Do not clone, animator will perform that clone.
(transFromStyleProps as any)[key] = enterFrom[key];
}
}
if (!isInit && fromElStyle) {
if (styleOpt.transition) {
const transitionKeys = normalizeToArray(styleOpt.transition);
!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
&& indexOf(elOption.transition, '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;
}
}
}
}
}
const leaveTo = styleOpt.leaveTo;
if (leaveTo) {
const leaveToKeys = keys(leaveTo);
const leaveToProps = getOrCreateLeaveToPropsFromEl(fromEl);
const leaveToStyleProps = leaveToProps.style || (leaveToProps.style = {});
for (let i = 0; i < leaveToKeys.length; i++) {
const key = leaveToKeys[i];
(leaveToStyleProps as any)[key] = leaveTo[key];
}
}
}
let checkNonStyleTansitionRefer: (propName: string, optVal: unknown, elVal: unknown) => void;
if (__DEV__) {
checkNonStyleTansitionRefer = function (propName: string, optVal: unknown, elVal: unknown): void {
if (!isArrayLike(optVal)) {
assert(
optVal != null && isFinite(optVal as number),
'Prop `' + propName + '` must refer to a finite number or ArrayLike for transition.'
);
}
else {
// Try not to copy array for performance, but if user use the same object in different
// call of `renderItem`, it will casue animation transition fail.
assert(
optVal !== elVal,
'Prop `' + propName + '` must use different Array object each time for transition.'
);
}
};
}
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 {
assert(
hasOwn(TRANSFORM_PROPS, key),
'Prop `' + key + '` is not a permitted in `' + usedIn + '`. '
+ 'Only `' + keys(TRANSFORM_PROPS).join('`, `') + '` are permitted.'
);
};
}
function getOrCreateLeaveToPropsFromEl(el: Element): LooseElementProps {
const innerEl = customInnerStore(el);
return innerEl.leaveToProps || (innerEl.leaveToProps = {});
}