import Transformable, {TRANSFORMABLE_PROPS, TransformProp} from './core/Transformable'; 
 | 
import { AnimationEasing } from './animation/easing'; 
 | 
import Animator, {cloneValue} from './animation/Animator'; 
 | 
import { ZRenderType } from './zrender'; 
 | 
import { 
 | 
    Dictionary, ElementEventName, ZRRawEvent, BuiltinTextPosition, AllPropTypes, 
 | 
    TextVerticalAlign, TextAlign, MapToType 
 | 
} from './core/types'; 
 | 
import Path from './graphic/Path'; 
 | 
import BoundingRect, { RectLike } from './core/BoundingRect'; 
 | 
import Eventful from './core/Eventful'; 
 | 
import ZRText, { DefaultTextStyle } from './graphic/Text'; 
 | 
import { calculateTextPosition, TextPositionCalculationResult, parsePercent } from './contain/text'; 
 | 
import { 
 | 
    guid, 
 | 
    isObject, 
 | 
    keys, 
 | 
    extend, 
 | 
    indexOf, 
 | 
    logError, 
 | 
    mixin, 
 | 
    isArrayLike, 
 | 
    isTypedArray, 
 | 
    isGradientObject, 
 | 
    filter, 
 | 
    reduce 
 | 
} from './core/util'; 
 | 
import Polyline from './graphic/shape/Polyline'; 
 | 
import Group from './graphic/Group'; 
 | 
import Point from './core/Point'; 
 | 
import { LIGHT_LABEL_COLOR, DARK_LABEL_COLOR } from './config'; 
 | 
import { parse, stringify } from './tool/color'; 
 | 
import { REDRAW_BIT } from './graphic/constants'; 
 | 
  
 | 
export interface ElementAnimateConfig { 
 | 
    duration?: number 
 | 
    delay?: number 
 | 
    easing?: AnimationEasing 
 | 
    during?: (percent: number) => void 
 | 
  
 | 
    // `done` will be called when all of the animations of the target props are 
 | 
    // "done" or "aborted", and at least one "done" happened. 
 | 
    // Common cases: animations declared, but some of them are aborted (e.g., by state change). 
 | 
    // The calling of `animationTo` done rather than aborted if at least one done happened. 
 | 
    done?: Function 
 | 
    // `aborted` will be called when all of the animations of the target props are "aborted". 
 | 
    aborted?: Function 
 | 
  
 | 
    scope?: string 
 | 
    /** 
 | 
     * If force animate 
 | 
     * Prevent stop animation and callback 
 | 
     * immediently when target values are the same as current values. 
 | 
     */ 
 | 
    force?: boolean 
 | 
    /** 
 | 
     * If use additive animation. 
 | 
     */ 
 | 
    additive?: boolean 
 | 
    /** 
 | 
     * If set to final state before animation started. 
 | 
     * It can be useful if something you want to calcuate depends on the final state of element. 
 | 
     * Like bounding rect for text layouting. 
 | 
     * 
 | 
     * Only available in animateTo 
 | 
     */ 
 | 
    setToFinal?: boolean 
 | 
} 
 | 
  
 | 
export interface ElementTextConfig { 
 | 
    /** 
 | 
     * Position relative to the element bounding rect 
 | 
     * @default 'inside' 
 | 
     */ 
 | 
    position?: BuiltinTextPosition | (number | string)[] 
 | 
  
 | 
    /** 
 | 
     * Rotation of the label. 
 | 
     */ 
 | 
    rotation?: number 
 | 
  
 | 
    /** 
 | 
     * Rect that text will be positioned. 
 | 
     * Default to be the rect of element. 
 | 
     */ 
 | 
    layoutRect?: RectLike 
 | 
  
 | 
    /** 
 | 
     * Offset of the label. 
 | 
     * The difference of offset and position is that it will be applied 
 | 
     * in the rotation 
 | 
     */ 
 | 
    offset?: number[] 
 | 
  
 | 
    /** 
 | 
     * Origin or rotation. Which is relative to the bounding box of the attached element. 
 | 
     * Can be percent value. Relative to the bounding box. 
 | 
     * If specified center. It will be center of the bounding box. 
 | 
     * 
 | 
     * Only available when position and rotation are both set. 
 | 
     */ 
 | 
    origin?: (number | string)[] | 'center' 
 | 
  
 | 
    /** 
 | 
     * Distance to the rect 
 | 
     * @default 5 
 | 
     */ 
 | 
    distance?: number 
 | 
  
 | 
    /** 
 | 
     * If use local user space. Which will apply host's transform 
 | 
     * @default false 
 | 
     */ 
 | 
    local?: boolean 
 | 
  
 | 
    /** 
 | 
     * `insideFill` is a color string or left empty. 
 | 
     * If a `textContent` is "inside", its final `fill` will be picked by this priority: 
 | 
     * `textContent.style.fill` > `textConfig.insideFill` > "auto-calculated-fill" 
 | 
     * In most cases, "auto-calculated-fill" is white. 
 | 
     */ 
 | 
    insideFill?: string 
 | 
  
 | 
    /** 
 | 
     * `insideStroke` is a color string or left empty. 
 | 
     * If a `textContent` is "inside", its final `stroke` will be picked by this priority: 
 | 
     * `textContent.style.stroke` > `textConfig.insideStroke` > "auto-calculated-stroke" 
 | 
     * 
 | 
     * The rule of getting "auto-calculated-stroke": 
 | 
     * If (A) the `fill` is specified in style (either in `textContent.style` or `textContent.style.rich`) 
 | 
     * or (B) needed to draw text background (either defined in `textContent.style` or `textContent.style.rich`) 
 | 
     * "auto-calculated-stroke" will be null. 
 | 
     * Otherwise, "auto-calculated-stroke" will be the same as `fill` of this element if possible, or null. 
 | 
     * 
 | 
     * The reason of (A) is not decisive: 
 | 
     * 1. If users specify `fill` in style and still use "auto-calculated-stroke", the effect 
 | 
     * is not good and unexpected in some cases. It not easy and seams uncessary to auto calculate 
 | 
     * a proper `stroke` for the given `fill`, since they can specify `stroke` themselve. 
 | 
     * 2. Backward compat. 
 | 
     */ 
 | 
    insideStroke?: string 
 | 
  
 | 
    /** 
 | 
     * `outsideFill` is a color string or left empty. 
 | 
     * If a `textContent` is "inside", its final `fill` will be picked by this priority: 
 | 
     * `textContent.style.fill` > `textConfig.outsideFill` > #000 
 | 
     */ 
 | 
    outsideFill?: string 
 | 
  
 | 
    /** 
 | 
     * `outsideStroke` is a color string or left empth. 
 | 
     * If a `textContent` is not "inside", its final `stroke` will be picked by this priority: 
 | 
     * `textContent.style.stroke` > `textConfig.outsideStroke` > "auto-calculated-stroke" 
 | 
     * 
 | 
     * The rule of getting "auto-calculated-stroke": 
 | 
     * If (A) the `fill` is specified in style (either in `textContent.style` or `textContent.style.rich`) 
 | 
     * or (B) needed to draw text background (either defined in `textContent.style` or `textContent.style.rich`) 
 | 
     * "auto-calculated-stroke" will be null. 
 | 
     * Otherwise, "auto-calculated-stroke" will be a neer white color to distinguish "front end" 
 | 
     * label with messy background (like other text label, line or other graphic). 
 | 
     */ 
 | 
    outsideStroke?: string 
 | 
  
 | 
    /** 
 | 
     * Tell zrender I can sure this text is inside or not. 
 | 
     * In case position is not using builtin `inside` hints. 
 | 
     */ 
 | 
    inside?: boolean 
 | 
} 
 | 
export interface ElementTextGuideLineConfig { 
 | 
    /** 
 | 
     * Anchor for text guide line. 
 | 
     * Notice: Won't work 
 | 
     */ 
 | 
    anchor?: Point 
 | 
  
 | 
    /** 
 | 
     * If above the target element. 
 | 
     */ 
 | 
    showAbove?: boolean 
 | 
  
 | 
    /** 
 | 
     * Candidates of connectors. Used when autoCalculate is true and anchor is not specified. 
 | 
     */ 
 | 
    candidates?: ('left' | 'top' | 'right' | 'bottom')[] 
 | 
} 
 | 
  
 | 
export interface ElementEvent { 
 | 
    type: ElementEventName, 
 | 
    event: ZRRawEvent, 
 | 
    // target can only be an element that is not silent. 
 | 
    target: Element, 
 | 
    // topTarget can be a silent element. 
 | 
    topTarget: Element, 
 | 
    cancelBubble: boolean, 
 | 
    offsetX: number, 
 | 
    offsetY: number, 
 | 
    gestureEvent: string, 
 | 
    pinchX: number, 
 | 
    pinchY: number, 
 | 
    pinchScale: number, 
 | 
    wheelDelta: number, 
 | 
    zrByTouch: boolean, 
 | 
    which: number, 
 | 
    stop: (this: ElementEvent) => void 
 | 
} 
 | 
  
 | 
export type ElementEventCallback<Ctx, Impl> = ( 
 | 
    this: CbThis<Ctx, Impl>, e: ElementEvent 
 | 
) => boolean | void 
 | 
type CbThis<Ctx, Impl> = unknown extends Ctx ? Impl : Ctx; 
 | 
  
 | 
interface ElementEventHandlerProps { 
 | 
    // Events 
 | 
    onclick: ElementEventCallback<unknown, unknown> 
 | 
    ondblclick: ElementEventCallback<unknown, unknown> 
 | 
    onmouseover: ElementEventCallback<unknown, unknown> 
 | 
    onmouseout: ElementEventCallback<unknown, unknown> 
 | 
    onmousemove: ElementEventCallback<unknown, unknown> 
 | 
    onmousewheel: ElementEventCallback<unknown, unknown> 
 | 
    onmousedown: ElementEventCallback<unknown, unknown> 
 | 
    onmouseup: ElementEventCallback<unknown, unknown> 
 | 
    oncontextmenu: ElementEventCallback<unknown, unknown> 
 | 
  
 | 
    ondrag: ElementEventCallback<unknown, unknown> 
 | 
    ondragstart: ElementEventCallback<unknown, unknown> 
 | 
    ondragend: ElementEventCallback<unknown, unknown> 
 | 
    ondragenter: ElementEventCallback<unknown, unknown> 
 | 
    ondragleave: ElementEventCallback<unknown, unknown> 
 | 
    ondragover: ElementEventCallback<unknown, unknown> 
 | 
    ondrop: ElementEventCallback<unknown, unknown> 
 | 
} 
 | 
  
 | 
export interface ElementProps extends Partial<ElementEventHandlerProps>, Partial<Pick<Transformable, TransformProp>> { 
 | 
    name?: string 
 | 
    ignore?: boolean 
 | 
    isGroup?: boolean 
 | 
    draggable?: boolean | 'horizontal' | 'vertical' 
 | 
  
 | 
    silent?: boolean 
 | 
  
 | 
    ignoreClip?: boolean 
 | 
    globalScaleRatio?: number 
 | 
  
 | 
    textConfig?: ElementTextConfig 
 | 
    textContent?: ZRText 
 | 
  
 | 
    clipPath?: Path 
 | 
    drift?: Element['drift'] 
 | 
  
 | 
    extra?: Dictionary<unknown> 
 | 
  
 | 
    // For echarts animation. 
 | 
    anid?: string 
 | 
} 
 | 
  
 | 
// Properties can be used in state. 
 | 
export const PRESERVED_NORMAL_STATE = '__zr_normal__'; 
 | 
// export const PRESERVED_MERGED_STATE = '__zr_merged__'; 
 | 
  
 | 
const PRIMARY_STATES_KEYS = (TRANSFORMABLE_PROPS as any).concat(['ignore']) as [TransformProp, 'ignore']; 
 | 
const DEFAULT_ANIMATABLE_MAP = reduce(TRANSFORMABLE_PROPS, (obj, key) => { 
 | 
    obj[key] = true; 
 | 
    return obj; 
 | 
}, {ignore: false} as Partial<Record<ElementStatePropNames, boolean>>); 
 | 
  
 | 
export type ElementStatePropNames = (typeof PRIMARY_STATES_KEYS)[number] | 'textConfig'; 
 | 
export type ElementState = Pick<ElementProps, ElementStatePropNames> & ElementCommonState 
 | 
  
 | 
export type ElementCommonState = { 
 | 
    hoverLayer?: boolean 
 | 
} 
 | 
  
 | 
export type ElementCalculateTextPosition = ( 
 | 
    out: TextPositionCalculationResult, 
 | 
    style: ElementTextConfig, 
 | 
    rect: RectLike 
 | 
) => TextPositionCalculationResult; 
 | 
  
 | 
let tmpTextPosCalcRes = {} as TextPositionCalculationResult; 
 | 
let tmpBoundingRect = new BoundingRect(0, 0, 0, 0); 
 | 
  
 | 
// eslint-disable-next-line @typescript-eslint/no-unused-vars 
 | 
interface Element<Props extends ElementProps = ElementProps> extends Transformable, 
 | 
    Eventful<{ 
 | 
        [key in ElementEventName]: (e: ElementEvent) => void | boolean 
 | 
    } & { 
 | 
        [key in string]: (...args: any) => void | boolean 
 | 
    }>, 
 | 
    ElementEventHandlerProps { 
 | 
} 
 | 
  
 | 
class Element<Props extends ElementProps = ElementProps> { 
 | 
  
 | 
    id: number = guid() 
 | 
    /** 
 | 
     * Element type 
 | 
     */ 
 | 
    type: string 
 | 
  
 | 
    /** 
 | 
     * Element name 
 | 
     */ 
 | 
    name: string 
 | 
  
 | 
    /** 
 | 
     * If ignore drawing and events of the element object 
 | 
     */ 
 | 
    ignore: boolean 
 | 
  
 | 
    /** 
 | 
     * Whether to respond to mouse events. 
 | 
     */ 
 | 
    silent: boolean 
 | 
  
 | 
    /** 
 | 
     * 是否是 Group 
 | 
     */ 
 | 
    isGroup: boolean 
 | 
  
 | 
    /** 
 | 
     * Whether it can be dragged. 
 | 
     */ 
 | 
    draggable: boolean | 'horizontal' | 'vertical' 
 | 
  
 | 
    /** 
 | 
     * Whether is it dragging. 
 | 
     */ 
 | 
    dragging: boolean 
 | 
  
 | 
    parent: Group 
 | 
  
 | 
    animators: Animator<any>[] = [] 
 | 
  
 | 
    /** 
 | 
     * If ignore clip from it's parent or hosts. 
 | 
     * Applied on itself and all it's children. 
 | 
     * 
 | 
     * NOTE: It won't affect the clipPath set on the children. 
 | 
     */ 
 | 
    ignoreClip: boolean 
 | 
  
 | 
    /** 
 | 
     * If element is used as a component of other element. 
 | 
     */ 
 | 
    __hostTarget: Element 
 | 
  
 | 
    /** 
 | 
     * ZRender instance will be assigned when element is associated with zrender 
 | 
     */ 
 | 
    __zr: ZRenderType 
 | 
  
 | 
    /** 
 | 
     * Dirty bits. 
 | 
     * From which painter will determine if this displayable object needs brush. 
 | 
     */ 
 | 
    __dirty: number 
 | 
  
 | 
    /** 
 | 
     * If element was painted on the screen 
 | 
     */ 
 | 
    __isRendered: boolean; 
 | 
  
 | 
    /** 
 | 
     * If element has been moved to the hover layer. 
 | 
     * 
 | 
     * If so, dirty will only trigger the zrender refresh hover layer 
 | 
     */ 
 | 
    __inHover: boolean 
 | 
  
 | 
    /** 
 | 
     * path to clip the elements and its children, if it is a group. 
 | 
     * @see http://www.w3.org/TR/2dcontext/#clipping-region 
 | 
     */ 
 | 
    private _clipPath?: Path 
 | 
  
 | 
    /** 
 | 
     * Attached text element. 
 | 
     * `position`, `style.textAlign`, `style.textVerticalAlign` 
 | 
     * of element will be ignored if textContent.position is set 
 | 
     */ 
 | 
    private _textContent?: ZRText 
 | 
  
 | 
    /** 
 | 
     * Text guide line. 
 | 
     */ 
 | 
    private _textGuide?: Polyline 
 | 
  
 | 
    /** 
 | 
     * Config of textContent. Inlcuding layout, color, ...etc. 
 | 
     */ 
 | 
    textConfig?: ElementTextConfig 
 | 
  
 | 
    /** 
 | 
     * Config for guide line calculating. 
 | 
     * 
 | 
     * NOTE: This is just a property signature. READ and WRITE are all done in echarts. 
 | 
     */ 
 | 
    textGuideLineConfig?: ElementTextGuideLineConfig 
 | 
  
 | 
    // FOR ECHARTS 
 | 
    /** 
 | 
     * Id for mapping animation 
 | 
     */ 
 | 
    anid: string 
 | 
  
 | 
    extra: Dictionary<unknown> 
 | 
  
 | 
    currentStates?: string[] = [] 
 | 
    // prevStates is for storager in echarts. 
 | 
    prevStates?: string[] 
 | 
    /** 
 | 
     * Store of element state. 
 | 
     * '__normal__' key is preserved for default properties. 
 | 
     */ 
 | 
    states: Dictionary<ElementState> = {} 
 | 
  
 | 
    /** 
 | 
     * Animation config applied on state switching. 
 | 
     */ 
 | 
    stateTransition: ElementAnimateConfig 
 | 
  
 | 
    /** 
 | 
     * Proxy function for getting state with given stateName. 
 | 
     * ZRender will first try to get with stateProxy. Then find from states if stateProxy returns nothing 
 | 
     * 
 | 
     * targetStates will be given in useStates 
 | 
     */ 
 | 
    stateProxy?: (stateName: string, targetStates?: string[]) => ElementState 
 | 
  
 | 
    protected _normalState: ElementState 
 | 
  
 | 
    // Temporary storage for inside text color configuration. 
 | 
    private _innerTextDefaultStyle: DefaultTextStyle 
 | 
  
 | 
    constructor(props?: Props) { 
 | 
        this._init(props); 
 | 
    } 
 | 
  
 | 
    protected _init(props?: Props) { 
 | 
        // Init default properties 
 | 
        this.attr(props); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Drift element 
 | 
     * @param {number} dx dx on the global space 
 | 
     * @param {number} dy dy on the global space 
 | 
     */ 
 | 
    drift(dx: number, dy: number, e?: ElementEvent) { 
 | 
        switch (this.draggable) { 
 | 
            case 'horizontal': 
 | 
                dy = 0; 
 | 
                break; 
 | 
            case 'vertical': 
 | 
                dx = 0; 
 | 
                break; 
 | 
        } 
 | 
  
 | 
        let m = this.transform; 
 | 
        if (!m) { 
 | 
            m = this.transform = [1, 0, 0, 1, 0, 0]; 
 | 
        } 
 | 
        m[4] += dx; 
 | 
        m[5] += dy; 
 | 
  
 | 
        this.decomposeTransform(); 
 | 
        this.markRedraw(); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Hook before update 
 | 
     */ 
 | 
    beforeUpdate() {} 
 | 
    /** 
 | 
     * Hook after update 
 | 
     */ 
 | 
    afterUpdate() {} 
 | 
    /** 
 | 
     * Update each frame 
 | 
     */ 
 | 
    update() { 
 | 
        this.updateTransform(); 
 | 
  
 | 
        if (this.__dirty) { 
 | 
            this.updateInnerText(); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    updateInnerText(forceUpdate?: boolean) { 
 | 
        // Update textContent 
 | 
        const textEl = this._textContent; 
 | 
        if (textEl && (!textEl.ignore || forceUpdate)) { 
 | 
            if (!this.textConfig) { 
 | 
                this.textConfig = {}; 
 | 
            } 
 | 
            const textConfig = this.textConfig; 
 | 
            const isLocal = textConfig.local; 
 | 
            const innerTransformable = textEl.innerTransformable; 
 | 
  
 | 
            let textAlign: TextAlign; 
 | 
            let textVerticalAlign: TextVerticalAlign; 
 | 
  
 | 
            let textStyleChanged = false; 
 | 
  
 | 
            // Apply host's transform. 
 | 
            innerTransformable.parent = isLocal ? this as unknown as Group : null; 
 | 
  
 | 
            let innerOrigin = false; 
 | 
  
 | 
            // Reset x/y/rotation 
 | 
            innerTransformable.copyTransform(textEl); 
 | 
  
 | 
            // Force set attached text's position if `position` is in config. 
 | 
            if (textConfig.position != null) { 
 | 
                let layoutRect = tmpBoundingRect; 
 | 
                if (textConfig.layoutRect) { 
 | 
                    layoutRect.copy(textConfig.layoutRect); 
 | 
                } 
 | 
                else { 
 | 
                    layoutRect.copy(this.getBoundingRect()); 
 | 
                } 
 | 
                if (!isLocal) { 
 | 
                    layoutRect.applyTransform(this.transform); 
 | 
                } 
 | 
  
 | 
                if (this.calculateTextPosition) { 
 | 
                    this.calculateTextPosition(tmpTextPosCalcRes, textConfig, layoutRect); 
 | 
                } 
 | 
                else { 
 | 
                    calculateTextPosition(tmpTextPosCalcRes, textConfig, layoutRect); 
 | 
                } 
 | 
  
 | 
                // TODO Should modify back if textConfig.position is set to null again. 
 | 
                // Or textContent is detached. 
 | 
                innerTransformable.x = tmpTextPosCalcRes.x; 
 | 
                innerTransformable.y = tmpTextPosCalcRes.y; 
 | 
  
 | 
                // User specified align/verticalAlign has higher priority, which is 
 | 
                // useful in the case that attached text is rotated 90 degree. 
 | 
                textAlign = tmpTextPosCalcRes.align; 
 | 
                textVerticalAlign = tmpTextPosCalcRes.verticalAlign; 
 | 
  
 | 
                const textOrigin = textConfig.origin; 
 | 
                if (textOrigin && textConfig.rotation != null) { 
 | 
                    let relOriginX; 
 | 
                    let relOriginY; 
 | 
                    if (textOrigin === 'center') { 
 | 
                        relOriginX = layoutRect.width * 0.5; 
 | 
                        relOriginY = layoutRect.height * 0.5; 
 | 
                    } 
 | 
                    else { 
 | 
                        relOriginX = parsePercent(textOrigin[0], layoutRect.width); 
 | 
                        relOriginY = parsePercent(textOrigin[1], layoutRect.height); 
 | 
                    } 
 | 
  
 | 
                    innerOrigin = true; 
 | 
                    innerTransformable.originX = -innerTransformable.x + relOriginX + (isLocal ? 0 : layoutRect.x); 
 | 
                    innerTransformable.originY = -innerTransformable.y + relOriginY + (isLocal ? 0 : layoutRect.y); 
 | 
                } 
 | 
            } 
 | 
  
 | 
  
 | 
            if (textConfig.rotation != null) { 
 | 
                innerTransformable.rotation = textConfig.rotation; 
 | 
            } 
 | 
  
 | 
            // TODO 
 | 
            const textOffset = textConfig.offset; 
 | 
            if (textOffset) { 
 | 
                innerTransformable.x += textOffset[0]; 
 | 
                innerTransformable.y += textOffset[1]; 
 | 
  
 | 
                // Not change the user set origin. 
 | 
                if (!innerOrigin) { 
 | 
                    innerTransformable.originX = -textOffset[0]; 
 | 
                    innerTransformable.originY = -textOffset[1]; 
 | 
                } 
 | 
            } 
 | 
  
 | 
            // Calculate text color 
 | 
            const isInside = textConfig.inside == null  // Force to be inside or not. 
 | 
                ? (typeof textConfig.position === 'string' && textConfig.position.indexOf('inside') >= 0) 
 | 
                : textConfig.inside; 
 | 
            const innerTextDefaultStyle = this._innerTextDefaultStyle || (this._innerTextDefaultStyle = {}); 
 | 
  
 | 
            let textFill; 
 | 
            let textStroke; 
 | 
            let autoStroke; 
 | 
            if (isInside && this.canBeInsideText()) { 
 | 
                // In most cases `textContent` need this "auto" strategy. 
 | 
                // So by default be 'auto'. Otherwise users need to literally 
 | 
                // set `insideFill: 'auto', insideStroke: 'auto'` each time. 
 | 
                textFill = textConfig.insideFill; 
 | 
                textStroke = textConfig.insideStroke; 
 | 
  
 | 
                if (textFill == null || textFill === 'auto') { 
 | 
                    textFill = this.getInsideTextFill(); 
 | 
                } 
 | 
                if (textStroke == null || textStroke === 'auto') { 
 | 
                    textStroke = this.getInsideTextStroke(textFill); 
 | 
                    autoStroke = true; 
 | 
                } 
 | 
            } 
 | 
            else { 
 | 
                textFill = textConfig.outsideFill; 
 | 
                textStroke = textConfig.outsideStroke; 
 | 
  
 | 
                if (textFill == null || textFill === 'auto') { 
 | 
                    textFill = this.getOutsideFill(); 
 | 
                } 
 | 
                // By default give a stroke to distinguish "front end" label with 
 | 
                // messy background (like other text label, line or other graphic). 
 | 
                // If textContent.style.fill specified, this auto stroke will not be used. 
 | 
                if (textStroke == null || textStroke === 'auto') { 
 | 
                    // If some time need to customize the default stroke getter, 
 | 
                    // add some kind of override method. 
 | 
                    textStroke = this.getOutsideStroke(textFill); 
 | 
                    autoStroke = true; 
 | 
                } 
 | 
            } 
 | 
            // Default `textFill` should must have a value to ensure text can be displayed. 
 | 
            textFill = textFill || '#000'; 
 | 
  
 | 
            if (textFill !== innerTextDefaultStyle.fill 
 | 
                || textStroke !== innerTextDefaultStyle.stroke 
 | 
                || autoStroke !== innerTextDefaultStyle.autoStroke 
 | 
                || textAlign !== innerTextDefaultStyle.align 
 | 
                || textVerticalAlign !== innerTextDefaultStyle.verticalAlign 
 | 
            ) { 
 | 
  
 | 
                textStyleChanged = true; 
 | 
  
 | 
                innerTextDefaultStyle.fill = textFill; 
 | 
                innerTextDefaultStyle.stroke = textStroke; 
 | 
                innerTextDefaultStyle.autoStroke = autoStroke; 
 | 
                innerTextDefaultStyle.align = textAlign; 
 | 
                innerTextDefaultStyle.verticalAlign = textVerticalAlign; 
 | 
  
 | 
                textEl.setDefaultTextStyle(innerTextDefaultStyle); 
 | 
            } 
 | 
  
 | 
            // Mark textEl to update transform. 
 | 
            // DON'T use markRedraw. It will cause Element itself to dirty again. 
 | 
            textEl.__dirty |= REDRAW_BIT; 
 | 
  
 | 
            if (textStyleChanged) { 
 | 
                // Only mark style dirty if necessary. Update ZRText is costly. 
 | 
                textEl.dirtyStyle(true); 
 | 
            } 
 | 
        } 
 | 
    } 
 | 
  
 | 
    protected canBeInsideText() { 
 | 
        return true; 
 | 
    } 
 | 
  
 | 
    protected getInsideTextFill(): string | undefined { 
 | 
        return '#fff'; 
 | 
    } 
 | 
  
 | 
    protected getInsideTextStroke(textFill: string): string | undefined { 
 | 
        return '#000'; 
 | 
    } 
 | 
  
 | 
    protected getOutsideFill(): string | undefined { 
 | 
        return this.__zr && this.__zr.isDarkMode() ? LIGHT_LABEL_COLOR : DARK_LABEL_COLOR; 
 | 
    } 
 | 
  
 | 
    protected getOutsideStroke(textFill: string): string { 
 | 
        const backgroundColor = this.__zr && this.__zr.getBackgroundColor(); 
 | 
        let colorArr = typeof backgroundColor === 'string' && parse(backgroundColor as string); 
 | 
        if (!colorArr) { 
 | 
            colorArr = [255, 255, 255, 1]; 
 | 
        } 
 | 
        // Assume blending on a white / black(dark) background. 
 | 
        const alpha = colorArr[3]; 
 | 
        const isDark = this.__zr.isDarkMode(); 
 | 
        for (let i = 0; i < 3; i++) { 
 | 
            colorArr[i] = colorArr[i] * alpha + (isDark ? 0 : 255) * (1 - alpha); 
 | 
        } 
 | 
        colorArr[3] = 1; 
 | 
        return stringify(colorArr, 'rgba'); 
 | 
    } 
 | 
  
 | 
    traverse<Context>( 
 | 
        cb: (this: Context, el: Element<Props>) => void, 
 | 
        context?: Context 
 | 
    ) {} 
 | 
  
 | 
    protected attrKV(key: string, value: unknown) { 
 | 
        if (key === 'textConfig') { 
 | 
            this.setTextConfig(value as ElementTextConfig); 
 | 
        } 
 | 
        else if (key === 'textContent') { 
 | 
            this.setTextContent(value as ZRText); 
 | 
        } 
 | 
        else if (key === 'clipPath') { 
 | 
            this.setClipPath(value as Path); 
 | 
        } 
 | 
        else if (key === 'extra') { 
 | 
            this.extra = this.extra || {}; 
 | 
            extend(this.extra, value); 
 | 
        } 
 | 
        else { 
 | 
            (this as any)[key] = value; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Hide the element 
 | 
     */ 
 | 
    hide() { 
 | 
        this.ignore = true; 
 | 
        this.markRedraw(); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Show the element 
 | 
     */ 
 | 
    show() { 
 | 
        this.ignore = false; 
 | 
        this.markRedraw(); 
 | 
    } 
 | 
  
 | 
    attr(keyOrObj: Props): this 
 | 
    attr<T extends keyof Props>(keyOrObj: T, value: Props[T]): this 
 | 
    attr(keyOrObj: keyof Props | Props, value?: unknown): this { 
 | 
        if (typeof keyOrObj === 'string') { 
 | 
            this.attrKV(keyOrObj as keyof ElementProps, value as AllPropTypes<ElementProps>); 
 | 
        } 
 | 
        else if (isObject(keyOrObj)) { 
 | 
            let obj = keyOrObj as object; 
 | 
            let keysArr = keys(obj); 
 | 
            for (let i = 0; i < keysArr.length; i++) { 
 | 
                let key = keysArr[i]; 
 | 
                this.attrKV(key as keyof ElementProps, keyOrObj[key]); 
 | 
            } 
 | 
        } 
 | 
        this.markRedraw(); 
 | 
        return this; 
 | 
    } 
 | 
  
 | 
    // Save current state to normal 
 | 
    saveCurrentToNormalState(toState: ElementState) { 
 | 
        this._innerSaveToNormal(toState); 
 | 
  
 | 
        // If we are switching from normal to other state during animation. 
 | 
        // We need to save final value of animation to the normal state. Not interpolated value. 
 | 
        const normalState = this._normalState; 
 | 
        for (let i = 0; i < this.animators.length; i++) { 
 | 
            const animator = this.animators[i]; 
 | 
            const fromStateTransition = animator.__fromStateTransition; 
 | 
            // Ignore animation from state transition(except normal). 
 | 
            // Ignore loop animation. 
 | 
            if (animator.getLoop() || fromStateTransition && fromStateTransition !== PRESERVED_NORMAL_STATE) { 
 | 
                continue; 
 | 
            } 
 | 
  
 | 
            const targetName = animator.targetName; 
 | 
            // Respecting the order of animation if multiple animator is 
 | 
            // animating on the same property(If additive animation is used) 
 | 
            const target = targetName 
 | 
                ? (normalState as any)[targetName] : normalState; 
 | 
            // Only save keys that are changed by the states. 
 | 
            animator.saveTo(target); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    protected _innerSaveToNormal(toState: ElementState) { 
 | 
        let normalState = this._normalState; 
 | 
        if (!normalState) { 
 | 
            // Clear previous stored normal states when switching from normalState to otherState. 
 | 
            normalState = this._normalState = {}; 
 | 
        } 
 | 
        if (toState.textConfig && !normalState.textConfig) { 
 | 
            normalState.textConfig = this.textConfig; 
 | 
        } 
 | 
  
 | 
        this._savePrimaryToNormal(toState, normalState, PRIMARY_STATES_KEYS); 
 | 
    } 
 | 
  
 | 
    protected _savePrimaryToNormal( 
 | 
        toState: Dictionary<any>, normalState: Dictionary<any>, primaryKeys: readonly string[] 
 | 
    ) { 
 | 
        for (let i = 0; i < primaryKeys.length; i++) { 
 | 
            let key = primaryKeys[i]; 
 | 
            // Only save property that will be changed by toState 
 | 
            // and has not been saved to normalState yet. 
 | 
            if (toState[key] != null && !(key in normalState)) { 
 | 
                (normalState as any)[key] = (this as any)[key]; 
 | 
            } 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * If has any state. 
 | 
     */ 
 | 
    hasState() { 
 | 
        return this.currentStates.length > 0; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Get state object 
 | 
     */ 
 | 
    getState(name: string) { 
 | 
        return this.states[name]; 
 | 
    } 
 | 
  
 | 
  
 | 
    /** 
 | 
     * Ensure state exists. If not, will create one and return. 
 | 
     */ 
 | 
    ensureState(name: string) { 
 | 
        const states = this.states; 
 | 
        if (!states[name]) { 
 | 
            states[name] = {}; 
 | 
        } 
 | 
        return states[name]; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Clear all states. 
 | 
     */ 
 | 
    clearStates(noAnimation?: boolean) { 
 | 
        this.useState(PRESERVED_NORMAL_STATE, false, noAnimation); 
 | 
        // TODO set _normalState to null? 
 | 
    } 
 | 
    /** 
 | 
     * Use state. State is a collection of properties. 
 | 
     * Will return current state object if state exists and stateName has been changed. 
 | 
     * 
 | 
     * @param stateName State name to be switched to 
 | 
     * @param keepCurrentState If keep current states. 
 | 
     *      If not, it will inherit from the normal state. 
 | 
     */ 
 | 
    useState(stateName: string, keepCurrentStates?: boolean, noAnimation?: boolean, forceUseHoverLayer?: boolean) { 
 | 
        // Use preserved word __normal__ 
 | 
        // TODO: Only restore changed properties when restore to normal??? 
 | 
        const toNormalState = stateName === PRESERVED_NORMAL_STATE; 
 | 
        const hasStates = this.hasState(); 
 | 
  
 | 
        if (!hasStates && toNormalState) { 
 | 
            // If switched from normal to normal. 
 | 
            return; 
 | 
        } 
 | 
  
 | 
        const currentStates = this.currentStates; 
 | 
        const animationCfg = this.stateTransition; 
 | 
  
 | 
        // No need to change in following cases: 
 | 
        // 1. Keep current states. and already being applied before. 
 | 
        // 2. Don't keep current states. And new state is same with the only one exists state. 
 | 
        if (indexOf(currentStates, stateName) >= 0 && (keepCurrentStates || currentStates.length === 1)) { 
 | 
            return; 
 | 
        } 
 | 
  
 | 
        let state; 
 | 
        if (this.stateProxy && !toNormalState) { 
 | 
            state = this.stateProxy(stateName); 
 | 
        } 
 | 
  
 | 
        if (!state) { 
 | 
            state = (this.states && this.states[stateName]); 
 | 
        } 
 | 
  
 | 
        if (!state && !toNormalState) { 
 | 
            logError(`State ${stateName} not exists.`); 
 | 
            return; 
 | 
        } 
 | 
  
 | 
        if (!toNormalState) { 
 | 
            this.saveCurrentToNormalState(state); 
 | 
        } 
 | 
  
 | 
        const useHoverLayer = !!((state && state.hoverLayer) || forceUseHoverLayer); 
 | 
  
 | 
        if (useHoverLayer) { 
 | 
            // Enter hover layer before states update. 
 | 
            this._toggleHoverLayerFlag(true); 
 | 
        } 
 | 
  
 | 
        this._applyStateObj( 
 | 
            stateName, 
 | 
            state, 
 | 
            this._normalState, 
 | 
            keepCurrentStates, 
 | 
            !noAnimation && !this.__inHover && animationCfg && animationCfg.duration > 0, 
 | 
            animationCfg 
 | 
        ); 
 | 
  
 | 
        // Also set text content. 
 | 
        const textContent = this._textContent; 
 | 
        const textGuide = this._textGuide; 
 | 
        if (textContent) { 
 | 
            // Force textContent use hover layer if self is using it. 
 | 
            textContent.useState(stateName, keepCurrentStates, noAnimation, useHoverLayer); 
 | 
        } 
 | 
        if (textGuide) { 
 | 
            textGuide.useState(stateName, keepCurrentStates, noAnimation, useHoverLayer); 
 | 
        } 
 | 
  
 | 
        if (toNormalState) { 
 | 
            // Clear state 
 | 
            this.currentStates = []; 
 | 
            // Reset normal state. 
 | 
            this._normalState = {}; 
 | 
        } 
 | 
        else { 
 | 
            if (!keepCurrentStates) { 
 | 
                this.currentStates = [stateName]; 
 | 
            } 
 | 
            else { 
 | 
                this.currentStates.push(stateName); 
 | 
            } 
 | 
        } 
 | 
  
 | 
        // Update animating target to the new object after state changed. 
 | 
        this._updateAnimationTargets(); 
 | 
  
 | 
        this.markRedraw(); 
 | 
  
 | 
        if (!useHoverLayer && this.__inHover) { 
 | 
            // Leave hover layer after states update and markRedraw. 
 | 
            this._toggleHoverLayerFlag(false); 
 | 
            // NOTE: avoid unexpected refresh when moving out from hover layer!! 
 | 
            // Only clear from hover layer. 
 | 
            this.__dirty &= ~REDRAW_BIT; 
 | 
        } 
 | 
  
 | 
        // Return used state. 
 | 
        return state; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Apply multiple states. 
 | 
     * @param states States list. 
 | 
     */ 
 | 
    useStates(states: string[], noAnimation?: boolean, forceUseHoverLayer?: boolean) { 
 | 
        if (!states.length) { 
 | 
            this.clearStates(); 
 | 
        } 
 | 
        else { 
 | 
            const stateObjects: ElementState[] = []; 
 | 
            const currentStates = this.currentStates; 
 | 
            const len = states.length; 
 | 
            let notChange = len === currentStates.length; 
 | 
            if (notChange) { 
 | 
                for (let i = 0; i < len; i++) { 
 | 
                    if (states[i] !== currentStates[i]) { 
 | 
                        notChange = false; 
 | 
                        break; 
 | 
                    } 
 | 
                } 
 | 
            } 
 | 
            if (notChange) { 
 | 
                return; 
 | 
            } 
 | 
  
 | 
            for (let i = 0; i < len; i++) { 
 | 
                const stateName = states[i]; 
 | 
                let stateObj: ElementState; 
 | 
                if (this.stateProxy) { 
 | 
                    stateObj = this.stateProxy(stateName, states); 
 | 
                } 
 | 
                if (!stateObj) { 
 | 
                    stateObj = this.states[stateName]; 
 | 
                } 
 | 
                if (stateObj) { 
 | 
                    stateObjects.push(stateObj); 
 | 
                } 
 | 
            } 
 | 
  
 | 
            const lastStateObj = stateObjects[len - 1]; 
 | 
            const useHoverLayer = !!((lastStateObj && lastStateObj.hoverLayer) || forceUseHoverLayer); 
 | 
            if (useHoverLayer) { 
 | 
                // Enter hover layer before states update. 
 | 
                this._toggleHoverLayerFlag(true); 
 | 
            } 
 | 
  
 | 
            const mergedState = this._mergeStates(stateObjects); 
 | 
            const animationCfg = this.stateTransition; 
 | 
  
 | 
            this.saveCurrentToNormalState(mergedState); 
 | 
  
 | 
            this._applyStateObj( 
 | 
                states.join(','), 
 | 
                mergedState, 
 | 
                this._normalState, 
 | 
                false, 
 | 
                !noAnimation && !this.__inHover && animationCfg && animationCfg.duration > 0, 
 | 
                animationCfg 
 | 
            ); 
 | 
  
 | 
            const textContent = this._textContent; 
 | 
            const textGuide = this._textGuide; 
 | 
            if (textContent) { 
 | 
                textContent.useStates(states, noAnimation, useHoverLayer); 
 | 
            } 
 | 
            if (textGuide) { 
 | 
                textGuide.useStates(states, noAnimation, useHoverLayer); 
 | 
            } 
 | 
  
 | 
            this._updateAnimationTargets(); 
 | 
  
 | 
            // Create a copy 
 | 
            this.currentStates = states.slice(); 
 | 
            this.markRedraw(); 
 | 
  
 | 
            if (!useHoverLayer && this.__inHover) { 
 | 
                // Leave hover layer after states update and markRedraw. 
 | 
                this._toggleHoverLayerFlag(false); 
 | 
                // NOTE: avoid unexpected refresh when moving out from hover layer!! 
 | 
                // Only clear from hover layer. 
 | 
                this.__dirty &= ~REDRAW_BIT; 
 | 
            } 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Return if el.silent or any ancestor element has silent true. 
 | 
     */ 
 | 
    isSilent() { 
 | 
        let isSilent = this.silent; 
 | 
        let ancestor = this.parent; 
 | 
        while (!isSilent && ancestor) { 
 | 
            if (ancestor.silent) { 
 | 
                isSilent = true; 
 | 
                break; 
 | 
            } 
 | 
            ancestor = ancestor.parent; 
 | 
        } 
 | 
        return isSilent; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Update animation targets when reference is changed. 
 | 
     */ 
 | 
    private _updateAnimationTargets() { 
 | 
        for (let i = 0; i < this.animators.length; i++) { 
 | 
            const animator = this.animators[i]; 
 | 
            if (animator.targetName) { 
 | 
                animator.changeTarget((this as any)[animator.targetName]); 
 | 
            } 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Remove state 
 | 
     * @param state State to remove 
 | 
     */ 
 | 
    removeState(state: string) { 
 | 
        const idx = indexOf(this.currentStates, state); 
 | 
        if (idx >= 0) { 
 | 
            const currentStates = this.currentStates.slice(); 
 | 
            currentStates.splice(idx, 1); 
 | 
            this.useStates(currentStates); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Replace exists state. 
 | 
     * @param oldState 
 | 
     * @param newState 
 | 
     * @param forceAdd If still add when even if replaced target not exists. 
 | 
     */ 
 | 
    replaceState(oldState: string, newState: string, forceAdd: boolean) { 
 | 
        const currentStates = this.currentStates.slice(); 
 | 
        const idx = indexOf(currentStates, oldState); 
 | 
        const newStateExists = indexOf(currentStates, newState) >= 0; 
 | 
        if (idx >= 0) { 
 | 
            if (!newStateExists) { 
 | 
                // Replace the old with the new one. 
 | 
                currentStates[idx] = newState; 
 | 
            } 
 | 
            else { 
 | 
                // Only remove the old one. 
 | 
                currentStates.splice(idx, 1); 
 | 
            } 
 | 
        } 
 | 
        else if (forceAdd && !newStateExists) { 
 | 
            currentStates.push(newState); 
 | 
        } 
 | 
        this.useStates(currentStates); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Toogle state. 
 | 
     */ 
 | 
    toggleState(state: string, enable: boolean) { 
 | 
        if (enable) { 
 | 
            this.useState(state, true); 
 | 
        } 
 | 
        else { 
 | 
            this.removeState(state); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    protected _mergeStates(states: ElementState[]) { 
 | 
        const mergedState: ElementState = {}; 
 | 
        let mergedTextConfig: ElementTextConfig; 
 | 
        for (let i = 0; i < states.length; i++) { 
 | 
            const state = states[i]; 
 | 
            extend(mergedState, state); 
 | 
  
 | 
            if (state.textConfig) { 
 | 
                mergedTextConfig = mergedTextConfig || {}; 
 | 
                extend(mergedTextConfig, state.textConfig); 
 | 
            } 
 | 
        } 
 | 
        if (mergedTextConfig) { 
 | 
            mergedState.textConfig = mergedTextConfig; 
 | 
        } 
 | 
  
 | 
        return mergedState; 
 | 
    } 
 | 
  
 | 
    protected _applyStateObj( 
 | 
        stateName: string, 
 | 
        state: ElementState, 
 | 
        normalState: ElementState, 
 | 
        keepCurrentStates: boolean, 
 | 
        transition: boolean, 
 | 
        animationCfg: ElementAnimateConfig 
 | 
    ) { 
 | 
        const needsRestoreToNormal = !(state && keepCurrentStates); 
 | 
  
 | 
        // TODO: Save current state to normal? 
 | 
        // TODO: Animation 
 | 
        if (state && state.textConfig) { 
 | 
            // Inherit from current state or normal state. 
 | 
            this.textConfig = extend( 
 | 
                {}, 
 | 
                keepCurrentStates ? this.textConfig : normalState.textConfig 
 | 
            ); 
 | 
            extend(this.textConfig, state.textConfig); 
 | 
        } 
 | 
        else if (needsRestoreToNormal) { 
 | 
            if (normalState.textConfig) {   // Only restore if changed and saved. 
 | 
                this.textConfig = normalState.textConfig; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        const transitionTarget: Dictionary<any> = {}; 
 | 
        let hasTransition = false; 
 | 
  
 | 
        for (let i = 0; i < PRIMARY_STATES_KEYS.length; i++) { 
 | 
            const key = PRIMARY_STATES_KEYS[i]; 
 | 
            const propNeedsTransition = transition && DEFAULT_ANIMATABLE_MAP[key]; 
 | 
  
 | 
            if (state && state[key] != null) { 
 | 
                if (propNeedsTransition) { 
 | 
                    hasTransition = true; 
 | 
                    transitionTarget[key] = state[key]; 
 | 
                } 
 | 
                else { 
 | 
                    // Replace if it exist in target state 
 | 
                    (this as any)[key] = state[key]; 
 | 
                } 
 | 
            } 
 | 
            else if (needsRestoreToNormal) { 
 | 
                if (normalState[key] != null) { 
 | 
                    if (propNeedsTransition) { 
 | 
                        hasTransition = true; 
 | 
                        transitionTarget[key] = normalState[key]; 
 | 
                    } 
 | 
                    else { 
 | 
                        // Restore to normal state 
 | 
                        (this as any)[key] = normalState[key]; 
 | 
                    } 
 | 
                } 
 | 
            } 
 | 
        } 
 | 
  
 | 
        if (!transition) { 
 | 
            // Keep the running animation to the new values after states changed. 
 | 
            // Not simply stop animation. Or it may have jump effect. 
 | 
            for (let i = 0; i < this.animators.length; i++) { 
 | 
                const animator = this.animators[i]; 
 | 
                const targetName = animator.targetName; 
 | 
                // Ignore loop animation 
 | 
                if (!animator.getLoop()) { 
 | 
                    animator.__changeFinalValue(targetName 
 | 
                        ? ((state || normalState) as any)[targetName] 
 | 
                        : (state || normalState) 
 | 
                    ); 
 | 
                } 
 | 
            } 
 | 
        } 
 | 
  
 | 
        if (hasTransition) { 
 | 
            this._transitionState( 
 | 
                stateName, 
 | 
                transitionTarget as Props, 
 | 
                animationCfg 
 | 
            ); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Component is some elements attached on this element for specific purpose. 
 | 
     * Like clipPath, textContent 
 | 
     */ 
 | 
    private _attachComponent(componentEl: Element) { 
 | 
        if (componentEl.__zr && !componentEl.__hostTarget) { 
 | 
            if (process.env.NODE_ENV !== 'production') { 
 | 
                throw new Error('Text element has been added to zrender.'); 
 | 
            } 
 | 
            return; 
 | 
        } 
 | 
  
 | 
        if (componentEl === this) { 
 | 
            if (process.env.NODE_ENV !== 'production') { 
 | 
                throw new Error('Recursive component attachment.'); 
 | 
            } 
 | 
            return; 
 | 
        } 
 | 
  
 | 
        const zr = this.__zr; 
 | 
        if (zr) { 
 | 
            // Needs to add self to zrender. For rerender triggering, or animation. 
 | 
            componentEl.addSelfToZr(zr); 
 | 
        } 
 | 
  
 | 
        componentEl.__zr = zr; 
 | 
        componentEl.__hostTarget = this as unknown as Element; 
 | 
    } 
 | 
  
 | 
    private _detachComponent(componentEl: Element) { 
 | 
        if (componentEl.__zr) { 
 | 
            componentEl.removeSelfFromZr(componentEl.__zr); 
 | 
        } 
 | 
  
 | 
        componentEl.__zr = null; 
 | 
        componentEl.__hostTarget = null; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Get clip path 
 | 
     */ 
 | 
    getClipPath() { 
 | 
        return this._clipPath; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Set clip path 
 | 
     * 
 | 
     * clipPath can't be shared between two elements. 
 | 
     */ 
 | 
    setClipPath(clipPath: Path) { 
 | 
        // Remove previous clip path 
 | 
        if (this._clipPath && this._clipPath !== clipPath) { 
 | 
            this.removeClipPath(); 
 | 
        } 
 | 
  
 | 
        this._attachComponent(clipPath); 
 | 
  
 | 
        this._clipPath = clipPath; 
 | 
        this.markRedraw(); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Remove clip path 
 | 
     */ 
 | 
    removeClipPath() { 
 | 
        const clipPath = this._clipPath; 
 | 
        if (clipPath) { 
 | 
            this._detachComponent(clipPath); 
 | 
            this._clipPath = null; 
 | 
            this.markRedraw(); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Get attached text content. 
 | 
     */ 
 | 
    getTextContent(): ZRText { 
 | 
        return this._textContent; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Attach text on element 
 | 
     */ 
 | 
    setTextContent(textEl: ZRText) { 
 | 
        const previousTextContent = this._textContent; 
 | 
        if (previousTextContent === textEl) { 
 | 
            return; 
 | 
        } 
 | 
        // Remove previous textContent 
 | 
        if (previousTextContent && previousTextContent !== textEl) { 
 | 
            this.removeTextContent(); 
 | 
        } 
 | 
        if (process.env.NODE_ENV !== 'production') { 
 | 
            if (textEl.__zr && !textEl.__hostTarget) { 
 | 
                throw new Error('Text element has been added to zrender.'); 
 | 
            } 
 | 
        } 
 | 
  
 | 
        textEl.innerTransformable = new Transformable(); 
 | 
  
 | 
        this._attachComponent(textEl); 
 | 
  
 | 
        this._textContent = textEl; 
 | 
  
 | 
        this.markRedraw(); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Set layout of attached text. Will merge with the previous. 
 | 
     */ 
 | 
    setTextConfig(cfg: ElementTextConfig) { 
 | 
        // TODO hide cfg property? 
 | 
        if (!this.textConfig) { 
 | 
            this.textConfig = {}; 
 | 
        } 
 | 
        extend(this.textConfig, cfg); 
 | 
        this.markRedraw(); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Remove text config 
 | 
     */ 
 | 
    removeTextConfig() { 
 | 
        this.textConfig = null; 
 | 
        this.markRedraw(); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Remove attached text element. 
 | 
     */ 
 | 
    removeTextContent() { 
 | 
        const textEl = this._textContent; 
 | 
        if (textEl) { 
 | 
            textEl.innerTransformable = null; 
 | 
            this._detachComponent(textEl); 
 | 
            this._textContent = null; 
 | 
            this._innerTextDefaultStyle = null; 
 | 
            this.markRedraw(); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    getTextGuideLine(): Polyline { 
 | 
        return this._textGuide; 
 | 
    } 
 | 
  
 | 
    setTextGuideLine(guideLine: Polyline) { 
 | 
        // Remove previous clip path 
 | 
        if (this._textGuide && this._textGuide !== guideLine) { 
 | 
            this.removeTextGuideLine(); 
 | 
        } 
 | 
  
 | 
        this._attachComponent(guideLine); 
 | 
  
 | 
        this._textGuide = guideLine; 
 | 
  
 | 
        this.markRedraw(); 
 | 
    } 
 | 
  
 | 
    removeTextGuideLine() { 
 | 
        const textGuide = this._textGuide; 
 | 
        if (textGuide) { 
 | 
            this._detachComponent(textGuide); 
 | 
            this._textGuide = null; 
 | 
            this.markRedraw(); 
 | 
        } 
 | 
    } 
 | 
    /** 
 | 
     * Mark element needs to be repainted 
 | 
     */ 
 | 
    markRedraw() { 
 | 
        this.__dirty |= REDRAW_BIT; 
 | 
        const zr = this.__zr; 
 | 
        if (zr) { 
 | 
            if (this.__inHover) { 
 | 
                zr.refreshHover(); 
 | 
            } 
 | 
            else { 
 | 
                zr.refresh(); 
 | 
            } 
 | 
        } 
 | 
  
 | 
        // Used as a clipPath or textContent 
 | 
        if (this.__hostTarget) { 
 | 
            this.__hostTarget.markRedraw(); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Besides marking elements to be refreshed. 
 | 
     * It will also invalid all cache and doing recalculate next frame. 
 | 
     */ 
 | 
    dirty() { 
 | 
        this.markRedraw(); 
 | 
    } 
 | 
  
 | 
    private _toggleHoverLayerFlag(inHover: boolean) { 
 | 
        this.__inHover = inHover; 
 | 
        const textContent = this._textContent; 
 | 
        const textGuide = this._textGuide; 
 | 
        if (textContent) { 
 | 
            textContent.__inHover = inHover; 
 | 
        } 
 | 
        if (textGuide) { 
 | 
            textGuide.__inHover = inHover; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Add self from zrender instance. 
 | 
     * Not recursively because it will be invoked when element added to storage. 
 | 
     */ 
 | 
    addSelfToZr(zr: ZRenderType) { 
 | 
        if (this.__zr === zr) { 
 | 
            return; 
 | 
        } 
 | 
  
 | 
        this.__zr = zr; 
 | 
        // 添加动画 
 | 
        const animators = this.animators; 
 | 
        if (animators) { 
 | 
            for (let i = 0; i < animators.length; i++) { 
 | 
                zr.animation.addAnimator(animators[i]); 
 | 
            } 
 | 
        } 
 | 
  
 | 
        if (this._clipPath) { 
 | 
            this._clipPath.addSelfToZr(zr); 
 | 
        } 
 | 
        if (this._textContent) { 
 | 
            this._textContent.addSelfToZr(zr); 
 | 
        } 
 | 
        if (this._textGuide) { 
 | 
            this._textGuide.addSelfToZr(zr); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Remove self from zrender instance. 
 | 
     * Not recursively because it will be invoked when element added to storage. 
 | 
     */ 
 | 
    removeSelfFromZr(zr: ZRenderType) { 
 | 
        if (!this.__zr) { 
 | 
            return; 
 | 
        } 
 | 
  
 | 
        this.__zr = null; 
 | 
        // Remove animation 
 | 
        const animators = this.animators; 
 | 
        if (animators) { 
 | 
            for (let i = 0; i < animators.length; i++) { 
 | 
                zr.animation.removeAnimator(animators[i]); 
 | 
            } 
 | 
        } 
 | 
  
 | 
        if (this._clipPath) { 
 | 
            this._clipPath.removeSelfFromZr(zr); 
 | 
        } 
 | 
        if (this._textContent) { 
 | 
            this._textContent.removeSelfFromZr(zr); 
 | 
        } 
 | 
        if (this._textGuide) { 
 | 
            this._textGuide.removeSelfFromZr(zr); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * 动画 
 | 
     * 
 | 
     * @param path The key to fetch value from object. Mostly style or shape. 
 | 
     * @param loop Whether to loop animation. 
 | 
     * @param allowDiscreteAnimation Whether to allow discrete animation 
 | 
     * @example: 
 | 
     *     el.animate('style', false) 
 | 
     *         .when(1000, {x: 10} ) 
 | 
     *         .done(function(){ // Animation done }) 
 | 
     *         .start() 
 | 
     */ 
 | 
    animate(key?: string, loop?: boolean, allowDiscreteAnimation?: boolean) { 
 | 
        let target = key ? (this as any)[key] : this; 
 | 
  
 | 
        if (process.env.NODE_ENV !== 'production') { 
 | 
            if (!target) { 
 | 
                logError( 
 | 
                    'Property "' 
 | 
                    + key 
 | 
                    + '" is not existed in element ' 
 | 
                    + this.id 
 | 
                ); 
 | 
                return; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        const animator = new Animator(target, loop, allowDiscreteAnimation); 
 | 
        key && (animator.targetName = key); 
 | 
        this.addAnimator(animator, key); 
 | 
        return animator; 
 | 
    } 
 | 
  
 | 
    addAnimator(animator: Animator<any>, key: string): void { 
 | 
        const zr = this.__zr; 
 | 
  
 | 
        const el = this; 
 | 
  
 | 
        animator.during(function () { 
 | 
            el.updateDuringAnimation(key as string); 
 | 
        }).done(function () { 
 | 
            const animators = el.animators; 
 | 
            // FIXME Animator will not be removed if use `Animator#stop` to stop animation 
 | 
            const idx = indexOf(animators, animator); 
 | 
            if (idx >= 0) { 
 | 
                animators.splice(idx, 1); 
 | 
            } 
 | 
        }); 
 | 
  
 | 
        this.animators.push(animator); 
 | 
  
 | 
        // If animate after added to the zrender 
 | 
        if (zr) { 
 | 
            zr.animation.addAnimator(animator); 
 | 
        } 
 | 
  
 | 
        // Wake up zrender to start the animation loop. 
 | 
        zr && zr.wakeUp(); 
 | 
    } 
 | 
  
 | 
    updateDuringAnimation(key: string) { 
 | 
        this.markRedraw(); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * 停止动画 
 | 
     * @param {boolean} forwardToLast If move to last frame before stop 
 | 
     */ 
 | 
    stopAnimation(scope?: string, forwardToLast?: boolean) { 
 | 
        const animators = this.animators; 
 | 
        const len = animators.length; 
 | 
        const leftAnimators: Animator<any>[] = []; 
 | 
        for (let i = 0; i < len; i++) { 
 | 
            const animator = animators[i]; 
 | 
            if (!scope || scope === animator.scope) { 
 | 
                animator.stop(forwardToLast); 
 | 
            } 
 | 
            else { 
 | 
                leftAnimators.push(animator); 
 | 
            } 
 | 
        } 
 | 
        this.animators = leftAnimators; 
 | 
  
 | 
        return this; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * @param animationProps A map to specify which property to animate. If not specified, will animate all. 
 | 
     * @example 
 | 
     *  // Animate position 
 | 
     *  el.animateTo({ 
 | 
     *      position: [10, 10] 
 | 
     *  }, { done: () => { // done } }) 
 | 
     * 
 | 
     *  // Animate shape, style and position in 100ms, delayed 100ms, with cubicOut easing 
 | 
     *  el.animateTo({ 
 | 
     *      shape: { 
 | 
     *          width: 500 
 | 
     *      }, 
 | 
     *      style: { 
 | 
     *          fill: 'red' 
 | 
     *      } 
 | 
     *      position: [10, 10] 
 | 
     *  }, { 
 | 
     *      duration: 100, 
 | 
     *      delay: 100, 
 | 
     *      easing: 'cubicOut', 
 | 
     *      done: () => { // done } 
 | 
     *  }) 
 | 
     */ 
 | 
    animateTo(target: Props, cfg?: ElementAnimateConfig, animationProps?: MapToType<Props, boolean>) { 
 | 
        animateTo(this, target, cfg, animationProps); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Animate from the target state to current state. 
 | 
     * The params and the value are the same as `this.animateTo`. 
 | 
     */ 
 | 
  
 | 
    // Overload definitions 
 | 
    animateFrom( 
 | 
        target: Props, cfg: ElementAnimateConfig, animationProps?: MapToType<Props, boolean> 
 | 
    ) { 
 | 
        animateTo(this, target, cfg, animationProps, true); 
 | 
    } 
 | 
  
 | 
    protected _transitionState( 
 | 
        stateName: string, target: Props, cfg?: ElementAnimateConfig, animationProps?: MapToType<Props, boolean> 
 | 
    ) { 
 | 
        const animators = animateTo(this, target, cfg, animationProps); 
 | 
        for (let i = 0; i < animators.length; i++) { 
 | 
            animators[i].__fromStateTransition = stateName; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Interface of getting the minimum bounding box. 
 | 
     */ 
 | 
    getBoundingRect(): BoundingRect { 
 | 
        return null; 
 | 
    } 
 | 
  
 | 
    getPaintRect(): BoundingRect { 
 | 
        return null; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * The string value of `textPosition` needs to be calculated to a real postion. 
 | 
     * For example, `'inside'` is calculated to `[rect.width/2, rect.height/2]` 
 | 
     * by default. See `contain/text.js#calculateTextPosition` for more details. 
 | 
     * But some coutom shapes like "pin", "flag" have center that is not exactly 
 | 
     * `[width/2, height/2]`. So we provide this hook to customize the calculation 
 | 
     * for those shapes. It will be called if the `style.textPosition` is a string. 
 | 
     * @param {Obejct} [out] Prepared out object. If not provided, this method should 
 | 
     *        be responsible for creating one. 
 | 
     * @param {module:zrender/graphic/Style} style 
 | 
     * @param {Object} rect {x, y, width, height} 
 | 
     * @return {Obejct} out The same as the input out. 
 | 
     *         { 
 | 
     *             x: number. mandatory. 
 | 
     *             y: number. mandatory. 
 | 
     *             align: string. optional. use style.textAlign by default. 
 | 
     *             verticalAlign: string. optional. use style.textVerticalAlign by default. 
 | 
     *         } 
 | 
     */ 
 | 
    calculateTextPosition: ElementCalculateTextPosition; 
 | 
  
 | 
    protected static initDefaultProps = (function () { 
 | 
        const elProto = Element.prototype; 
 | 
        elProto.type = 'element'; 
 | 
        elProto.name = ''; 
 | 
  
 | 
        elProto.ignore = 
 | 
        elProto.silent = 
 | 
        elProto.isGroup = 
 | 
        elProto.draggable = 
 | 
        elProto.dragging = 
 | 
        elProto.ignoreClip = 
 | 
        elProto.__inHover = false; 
 | 
  
 | 
        elProto.__dirty = REDRAW_BIT; 
 | 
  
 | 
  
 | 
        const logs: Dictionary<boolean> = {}; 
 | 
        function logDeprecatedError(key: string, xKey: string, yKey: string) { 
 | 
            if (!logs[key + xKey + yKey]) { 
 | 
                console.warn(`DEPRECATED: '${key}' has been deprecated. use '${xKey}', '${yKey}' instead`); 
 | 
                logs[key + xKey + yKey] = true; 
 | 
            } 
 | 
        } 
 | 
        // Legacy transform properties. position and scale 
 | 
        function createLegacyProperty( 
 | 
            key: string, 
 | 
            privateKey: string, 
 | 
            xKey: string, 
 | 
            yKey: string 
 | 
        ) { 
 | 
            Object.defineProperty(elProto, key, { 
 | 
                get() { 
 | 
                    if (process.env.NODE_ENV !== 'production') { 
 | 
                        logDeprecatedError(key, xKey, yKey); 
 | 
                    } 
 | 
                    if (!this[privateKey]) { 
 | 
                        const pos: number[] = this[privateKey] = []; 
 | 
                        enhanceArray(this, pos); 
 | 
                    } 
 | 
                    return this[privateKey]; 
 | 
                }, 
 | 
                set(pos: number[]) { 
 | 
                    if (process.env.NODE_ENV !== 'production') { 
 | 
                        logDeprecatedError(key, xKey, yKey); 
 | 
                    } 
 | 
                    this[xKey] = pos[0]; 
 | 
                    this[yKey] = pos[1]; 
 | 
                    this[privateKey] = pos; 
 | 
                    enhanceArray(this, pos); 
 | 
                } 
 | 
            }); 
 | 
            function enhanceArray(self: any, pos: number[]) { 
 | 
                Object.defineProperty(pos, 0, { 
 | 
                    get() { 
 | 
                        return self[xKey]; 
 | 
                    }, 
 | 
                    set(val: number) { 
 | 
                        self[xKey] = val; 
 | 
                    } 
 | 
                }); 
 | 
                Object.defineProperty(pos, 1, { 
 | 
                    get() { 
 | 
                        return self[yKey]; 
 | 
                    }, 
 | 
                    set(val: number) { 
 | 
                        self[yKey] = val; 
 | 
                    } 
 | 
                }); 
 | 
            } 
 | 
        } 
 | 
        if (Object.defineProperty 
 | 
            // Just don't support ie8 
 | 
            // && (!(env as any).browser.ie || (env as any).browser.version > 8) 
 | 
        ) { 
 | 
            createLegacyProperty('position', '_legacyPos', 'x', 'y'); 
 | 
            createLegacyProperty('scale', '_legacyScale', 'scaleX', 'scaleY'); 
 | 
            createLegacyProperty('origin', '_legacyOrigin', 'originX', 'originY'); 
 | 
        } 
 | 
    })() 
 | 
} 
 | 
  
 | 
mixin(Element, Eventful); 
 | 
mixin(Element, Transformable); 
 | 
  
 | 
function animateTo<T>( 
 | 
    animatable: Element<T>, 
 | 
    target: Dictionary<any>, 
 | 
    cfg: ElementAnimateConfig, 
 | 
    animationProps: Dictionary<any>, 
 | 
    reverse?: boolean 
 | 
) { 
 | 
    cfg = cfg || {}; 
 | 
    const animators: Animator<any>[] = []; 
 | 
    animateToShallow( 
 | 
        animatable, 
 | 
        '', 
 | 
        animatable, 
 | 
        target, 
 | 
        cfg, 
 | 
        animationProps, 
 | 
        animators, 
 | 
        reverse 
 | 
    ); 
 | 
  
 | 
    let finishCount = animators.length; 
 | 
    let doneHappened = false; 
 | 
    const cfgDone = cfg.done; 
 | 
    const cfgAborted = cfg.aborted; 
 | 
  
 | 
    const doneCb = () => { 
 | 
        doneHappened = true; 
 | 
        finishCount--; 
 | 
        if (finishCount <= 0) { 
 | 
            doneHappened 
 | 
                ? (cfgDone && cfgDone()) 
 | 
                : (cfgAborted && cfgAborted()); 
 | 
        } 
 | 
    }; 
 | 
  
 | 
    const abortedCb = () => { 
 | 
        finishCount--; 
 | 
        if (finishCount <= 0) { 
 | 
            doneHappened 
 | 
                ? (cfgDone && cfgDone()) 
 | 
                : (cfgAborted && cfgAborted()); 
 | 
        } 
 | 
    }; 
 | 
  
 | 
    // No animators. This should be checked before animators[i].start(), 
 | 
    // because 'done' may be executed immediately if no need to animate. 
 | 
    if (!finishCount) { 
 | 
        cfgDone && cfgDone(); 
 | 
    } 
 | 
  
 | 
    // Adding during callback to the first animator 
 | 
    if (animators.length > 0 && cfg.during) { 
 | 
        // TODO If there are two animators in animateTo, and the first one is stopped by other animator. 
 | 
        animators[0].during((target, percent) => { 
 | 
            cfg.during(percent); 
 | 
        }); 
 | 
    } 
 | 
  
 | 
    // Start after all animators created 
 | 
    // Incase any animator is done immediately when all animation properties are not changed 
 | 
    for (let i = 0; i < animators.length; i++) { 
 | 
        const animator = animators[i]; 
 | 
        if (doneCb) { 
 | 
            animator.done(doneCb); 
 | 
        } 
 | 
        if (abortedCb) { 
 | 
            animator.aborted(abortedCb); 
 | 
        } 
 | 
        if (cfg.force) { 
 | 
            animator.duration(cfg.duration); 
 | 
        } 
 | 
        animator.start(cfg.easing); 
 | 
    } 
 | 
  
 | 
    return animators; 
 | 
} 
 | 
  
 | 
function copyArrShallow(source: number[], target: number[], len: number) { 
 | 
    for (let i = 0; i < len; i++) { 
 | 
        source[i] = target[i]; 
 | 
    } 
 | 
} 
 | 
  
 | 
function is2DArray(value: any[]): value is number[][] { 
 | 
    return isArrayLike(value[0]); 
 | 
} 
 | 
  
 | 
function copyValue(target: Dictionary<any>, source: Dictionary<any>, key: string) { 
 | 
    if (isArrayLike(source[key])) { 
 | 
        if (!isArrayLike(target[key])) { 
 | 
            target[key] = []; 
 | 
        } 
 | 
  
 | 
        if (isTypedArray(source[key])) { 
 | 
            const len = source[key].length; 
 | 
            if (target[key].length !== len) { 
 | 
                target[key] = new (source[key].constructor)(len); 
 | 
                copyArrShallow(target[key], source[key], len); 
 | 
            } 
 | 
        } 
 | 
        else { 
 | 
            const sourceArr = source[key] as any[]; 
 | 
            const targetArr = target[key] as any[]; 
 | 
  
 | 
            const len0 = sourceArr.length; 
 | 
            if (is2DArray(sourceArr)) { 
 | 
                // NOTE: each item should have same length 
 | 
                const len1 = sourceArr[0].length; 
 | 
  
 | 
                for (let i = 0; i < len0; i++) { 
 | 
                    if (!targetArr[i]) { 
 | 
                        targetArr[i] = Array.prototype.slice.call(sourceArr[i]); 
 | 
                    } 
 | 
                    else { 
 | 
                        copyArrShallow(targetArr[i], sourceArr[i], len1); 
 | 
                    } 
 | 
                } 
 | 
            } 
 | 
            else { 
 | 
                copyArrShallow(targetArr, sourceArr, len0); 
 | 
            } 
 | 
  
 | 
            targetArr.length = sourceArr.length; 
 | 
        } 
 | 
    } 
 | 
    else { 
 | 
        target[key] = source[key]; 
 | 
    } 
 | 
} 
 | 
  
 | 
function isValueSame(val1: any, val2: any) { 
 | 
    return val1 === val2 
 | 
        // Only check 1 dimension array 
 | 
        || isArrayLike(val1) && isArrayLike(val2) && is1DArraySame(val1, val2); 
 | 
} 
 | 
  
 | 
function is1DArraySame(arr0: ArrayLike<number>, arr1: ArrayLike<number>) { 
 | 
    const len = arr0.length; 
 | 
    if (len !== arr1.length) { 
 | 
        return false; 
 | 
    } 
 | 
    for (let i = 0; i < len; i++) { 
 | 
        if (arr0[i] !== arr1[i]) { 
 | 
            return false; 
 | 
        } 
 | 
    } 
 | 
    return true; 
 | 
} 
 | 
  
 | 
function animateToShallow<T>( 
 | 
    animatable: Element<T>, 
 | 
    topKey: string, 
 | 
    animateObj: Dictionary<any>, 
 | 
    target: Dictionary<any>, 
 | 
    cfg: ElementAnimateConfig, 
 | 
    animationProps: Dictionary<any> | true, 
 | 
    animators: Animator<any>[], 
 | 
    reverse: boolean    // If `true`, animate from the `target` to current state. 
 | 
) { 
 | 
    const targetKeys = keys(target); 
 | 
    const duration = cfg.duration; 
 | 
    const delay = cfg.delay; 
 | 
    const additive = cfg.additive; 
 | 
    const setToFinal = cfg.setToFinal; 
 | 
    const animateAll = !isObject(animationProps); 
 | 
    // Find last animator animating same prop. 
 | 
    const existsAnimators = animatable.animators; 
 | 
  
 | 
    let animationKeys: string[] = []; 
 | 
    for (let k = 0; k < targetKeys.length; k++) { 
 | 
        const innerKey = targetKeys[k] as string; 
 | 
        const targetVal = target[innerKey]; 
 | 
  
 | 
        if ( 
 | 
            targetVal != null && animateObj[innerKey] != null 
 | 
            && (animateAll || (animationProps as Dictionary<any>)[innerKey]) 
 | 
        ) { 
 | 
            if (isObject(targetVal) 
 | 
                && !isArrayLike(targetVal) 
 | 
                && !isGradientObject(targetVal) 
 | 
            ) { 
 | 
                if (topKey) { 
 | 
                    // logError('Only support 1 depth nest object animation.'); 
 | 
                    // Assign directly. 
 | 
                    // TODO richText? 
 | 
                    if (!reverse) { 
 | 
                        animateObj[innerKey] = targetVal; 
 | 
                        animatable.updateDuringAnimation(topKey); 
 | 
                    } 
 | 
                    continue; 
 | 
                } 
 | 
                animateToShallow( 
 | 
                    animatable, 
 | 
                    innerKey, 
 | 
                    animateObj[innerKey], 
 | 
                    targetVal, 
 | 
                    cfg, 
 | 
                    animationProps && (animationProps as Dictionary<any>)[innerKey], 
 | 
                    animators, 
 | 
                    reverse 
 | 
                ); 
 | 
            } 
 | 
            else { 
 | 
                animationKeys.push(innerKey); 
 | 
            } 
 | 
        } 
 | 
        else if (!reverse) { 
 | 
            // Assign target value directly. 
 | 
            animateObj[innerKey] = targetVal; 
 | 
            animatable.updateDuringAnimation(topKey); 
 | 
            // Previous animation will be stopped on the changed keys. 
 | 
            // So direct assign is also included. 
 | 
            animationKeys.push(innerKey); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    let keyLen = animationKeys.length; 
 | 
    // Stop previous animations on the same property. 
 | 
    if (!additive && keyLen) { 
 | 
        // Stop exists animation on specific tracks. Only one animator available for each property. 
 | 
        // TODO Should invoke previous animation callback? 
 | 
        for (let i = 0; i < existsAnimators.length; i++) { 
 | 
            const animator = existsAnimators[i]; 
 | 
            if (animator.targetName === topKey) { 
 | 
                const allAborted = animator.stopTracks(animationKeys); 
 | 
                if (allAborted) {   // This animator can't be used. 
 | 
                    const idx = indexOf(existsAnimators, animator); 
 | 
                    existsAnimators.splice(idx, 1); 
 | 
                } 
 | 
            } 
 | 
        } 
 | 
    } 
 | 
  
 | 
    // Ignore values not changed. 
 | 
    // NOTE: Must filter it after previous animation stopped 
 | 
    // and make sure the value to compare is using initial frame if animation is not started yet when setToFinal is used. 
 | 
    if (!cfg.force) { 
 | 
        animationKeys = filter(animationKeys, key => !isValueSame(target[key], animateObj[key])); 
 | 
        keyLen = animationKeys.length; 
 | 
    } 
 | 
  
 | 
    if (keyLen > 0 
 | 
        // cfg.force is mainly for keep invoking onframe and ondone callback even if animation is not necessary. 
 | 
        // So if there is already has animators. There is no need to create another animator if not necessary. 
 | 
        // Or it will always add one more with empty target. 
 | 
        || (cfg.force && !animators.length) 
 | 
    ) { 
 | 
        let revertedSource: Dictionary<any>; 
 | 
        let reversedTarget: Dictionary<any>; 
 | 
        let sourceClone: Dictionary<any>; 
 | 
        if (reverse) { 
 | 
            reversedTarget = {}; 
 | 
            if (setToFinal) { 
 | 
                revertedSource = {}; 
 | 
            } 
 | 
            for (let i = 0; i < keyLen; i++) { 
 | 
                const innerKey = animationKeys[i]; 
 | 
                reversedTarget[innerKey] = animateObj[innerKey]; 
 | 
                if (setToFinal) { 
 | 
                    revertedSource[innerKey] = target[innerKey]; 
 | 
                } 
 | 
                else { 
 | 
                    // The usage of "animateFrom" expects that the element props has been updated dirctly to 
 | 
                    // "final" values outside, and input the "from" values here (i.e., in variable `target` here). 
 | 
                    // So here we assign the "from" values directly to element here (rather that in the next frame) 
 | 
                    // to prevent the "final" values from being read in any other places (like other running 
 | 
                    // animator during callbacks). 
 | 
                    // But if `setToFinal: true` this feature can not be satisfied. 
 | 
                    animateObj[innerKey] = target[innerKey]; 
 | 
                } 
 | 
            } 
 | 
        } 
 | 
        else if (setToFinal) { 
 | 
            sourceClone = {}; 
 | 
            for (let i = 0; i < keyLen; i++) { 
 | 
                const innerKey = animationKeys[i]; 
 | 
                // NOTE: Must clone source after the stopTracks. The property may be modified in stopTracks. 
 | 
                sourceClone[innerKey] = cloneValue(animateObj[innerKey]); 
 | 
                // Use copy, not change the original reference 
 | 
                // Copy from target to source. 
 | 
                copyValue(animateObj, target, innerKey); 
 | 
            } 
 | 
        } 
 | 
  
 | 
        const animator = new Animator(animateObj, false, false, additive ? filter( 
 | 
            // Use key string instead object reference because ref may be changed. 
 | 
            existsAnimators, animator => animator.targetName === topKey 
 | 
        ) : null); 
 | 
  
 | 
        animator.targetName = topKey; 
 | 
        if (cfg.scope) { 
 | 
            animator.scope = cfg.scope; 
 | 
        } 
 | 
  
 | 
        if (setToFinal && revertedSource) { 
 | 
            animator.whenWithKeys(0, revertedSource, animationKeys); 
 | 
        } 
 | 
        if (sourceClone) { 
 | 
            animator.whenWithKeys(0, sourceClone, animationKeys); 
 | 
        } 
 | 
  
 | 
        animator.whenWithKeys( 
 | 
            duration == null ? 500 : duration, 
 | 
            reverse ? reversedTarget : target, 
 | 
            animationKeys 
 | 
        ).delay(delay || 0); 
 | 
  
 | 
        animatable.addAnimator(animator, topKey); 
 | 
        animators.push(animator); 
 | 
    } 
 | 
} 
 | 
  
 | 
  
 | 
export default Element; 
 |