Element.ts 71 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172
  1. import Transformable, {TRANSFORMABLE_PROPS, TransformProp} from './core/Transformable';
  2. import { AnimationEasing } from './animation/easing';
  3. import Animator, {cloneValue} from './animation/Animator';
  4. import { ZRenderType } from './zrender';
  5. import {
  6. Dictionary, ElementEventName, ZRRawEvent, BuiltinTextPosition, AllPropTypes,
  7. TextVerticalAlign, TextAlign, MapToType,
  8. } from './core/types';
  9. import Path from './graphic/Path';
  10. import BoundingRect, { RectLike } from './core/BoundingRect';
  11. import Eventful from './core/Eventful';
  12. import ZRText, { DefaultTextStyle } from './graphic/Text';
  13. import { calculateTextPosition, TextPositionCalculationResult, parsePercent } from './contain/text';
  14. import {
  15. guid,
  16. isObject,
  17. keys,
  18. extend,
  19. indexOf,
  20. logError,
  21. mixin,
  22. isArrayLike,
  23. isTypedArray,
  24. isGradientObject,
  25. filter,
  26. reduce
  27. } from './core/util';
  28. import Polyline from './graphic/shape/Polyline';
  29. import Group from './graphic/Group';
  30. import Point from './core/Point';
  31. import { LIGHT_LABEL_COLOR, DARK_LABEL_COLOR } from './config';
  32. import { parse, stringify } from './tool/color';
  33. import { REDRAW_BIT } from './graphic/constants';
  34. import { invert } from './core/matrix';
  35. export interface ElementAnimateConfig {
  36. duration?: number
  37. delay?: number
  38. easing?: AnimationEasing
  39. during?: (percent: number) => void
  40. // `done` will be called when all of the animations of the target props are
  41. // "done" or "aborted", and at least one "done" happened.
  42. // Common cases: animations declared, but some of them are aborted (e.g., by state change).
  43. // The calling of `animationTo` done rather than aborted if at least one done happened.
  44. done?: Function
  45. // `aborted` will be called when all of the animations of the target props are "aborted".
  46. aborted?: Function
  47. scope?: string
  48. /**
  49. * If force animate
  50. * Prevent stop animation and callback
  51. * immediently when target values are the same as current values.
  52. */
  53. force?: boolean
  54. /**
  55. * If use additive animation.
  56. */
  57. additive?: boolean
  58. /**
  59. * If set to final state before animation started.
  60. * It can be useful if something you want to calcuate depends on the final state of element.
  61. * Like bounding rect for text layouting.
  62. *
  63. * Only available in animateTo
  64. */
  65. setToFinal?: boolean
  66. }
  67. export interface ElementTextConfig {
  68. /**
  69. * Position relative to the element bounding rect
  70. * @default 'inside'
  71. */
  72. position?: BuiltinTextPosition | (number | string)[]
  73. /**
  74. * Rotation of the label.
  75. */
  76. rotation?: number
  77. /**
  78. * Rect that text will be positioned.
  79. * Default to be the boundingRect of the host element.
  80. * The coords of `layoutRect` is based on the target element, but not global.
  81. *
  82. * [NOTICE]: boundingRect includes `lineWidth`, which is inconsistent with
  83. * the general element placement principle, where `lineWidth` is not counted.
  84. */
  85. layoutRect?: RectLike
  86. /**
  87. * Offset of the label.
  88. * The difference of offset and position is that it will be applied
  89. * in the rotation
  90. */
  91. offset?: number[]
  92. /**
  93. * Origin or rotation. Which is relative to the bounding box of the attached element.
  94. * Can be percent value. Relative to the bounding box.
  95. * If specified center. It will be center of the bounding box.
  96. *
  97. * Only available when position and rotation are both set.
  98. */
  99. origin?: (number | string)[] | 'center'
  100. /**
  101. * Distance to the rect
  102. * @default 5
  103. */
  104. distance?: number
  105. /**
  106. * If use local user space. Which will apply host's transform
  107. *
  108. * [NOTICE]: If the host element may rotate to non-parallel to screen x/y,
  109. * need to use `local:true`, otherwise the transformed layout rect may not be expected.
  110. *
  111. * @default false
  112. */
  113. local?: boolean
  114. /**
  115. * `insideFill` is a color string or left empty.
  116. * If a `textContent` is "inside", its final `fill` will be picked by this priority:
  117. * `textContent.style.fill` > `textConfig.insideFill` > "auto-calculated-fill"
  118. * In most cases, "auto-calculated-fill" is white.
  119. */
  120. insideFill?: string
  121. /**
  122. * `insideStroke` is a color string or left empty.
  123. * If a `textContent` is "inside", its final `stroke` will be picked by this priority:
  124. * `textContent.style.stroke` > `textConfig.insideStroke` > "auto-calculated-stroke"
  125. *
  126. * The rule of getting "auto-calculated-stroke":
  127. * If (A) the `fill` is specified in style (either in `textContent.style` or `textContent.style.rich`)
  128. * or (B) needed to draw text background (either defined in `textContent.style` or `textContent.style.rich`)
  129. * "auto-calculated-stroke" will be null.
  130. * Otherwise, "auto-calculated-stroke" will be the same as `fill` of this element if possible, or null.
  131. *
  132. * The reason of (A) is not decisive:
  133. * 1. If users specify `fill` in style and still use "auto-calculated-stroke", the effect
  134. * is not good and unexpected in some cases. It not easy and seams uncessary to auto calculate
  135. * a proper `stroke` for the given `fill`, since they can specify `stroke` themselve.
  136. * 2. Backward compat.
  137. */
  138. insideStroke?: string
  139. /**
  140. * `outsideFill` is a color string or left empty.
  141. * If a `textContent` is "inside", its final `fill` will be picked by this priority:
  142. * `textContent.style.fill` > `textConfig.outsideFill` > #000
  143. */
  144. outsideFill?: string
  145. /**
  146. * `outsideStroke` is a color string or left empth.
  147. * If a `textContent` is not "inside", its final `stroke` will be picked by this priority:
  148. * `textContent.style.stroke` > `textConfig.outsideStroke` > "auto-calculated-stroke"
  149. *
  150. * The rule of getting "auto-calculated-stroke":
  151. * If (A) the `fill` is specified in style (either in `textContent.style` or `textContent.style.rich`)
  152. * or (B) needed to draw text background (either defined in `textContent.style` or `textContent.style.rich`)
  153. * "auto-calculated-stroke" will be null.
  154. * Otherwise, "auto-calculated-stroke" will be a neer white color to distinguish "front end"
  155. * label with messy background (like other text label, line or other graphic).
  156. */
  157. outsideStroke?: string
  158. /**
  159. * Tell zrender I can sure this text is inside or not.
  160. * In case position is not using builtin `inside` hints.
  161. */
  162. inside?: boolean
  163. /**
  164. * Auto calculate overflow area by `textConfig.layoutRect` (if any) or `host.boundingRect`.
  165. * It makes sense only if label is inside. It ensure the text does not overflow the host.
  166. * Useful in `text.style.overflow` and `text.style.lineOverflow`.
  167. *
  168. * If `textConfig.rotation` or `text.rotation exists`, it works correctly only when the rotated text is parallel
  169. * to its host (i.e. 0, PI/2, PI, PI*3/2, 2*PI, ...). Do not supported other cases until a real scenario arises.
  170. */
  171. autoOverflowArea?: boolean
  172. }
  173. export interface ElementTextGuideLineConfig {
  174. /**
  175. * Anchor for text guide line.
  176. * Notice: Won't work
  177. */
  178. anchor?: Point
  179. /**
  180. * If above the target element.
  181. */
  182. showAbove?: boolean
  183. /**
  184. * Candidates of connectors. Used when autoCalculate is true and anchor is not specified.
  185. */
  186. candidates?: ('left' | 'top' | 'right' | 'bottom')[]
  187. }
  188. export interface ElementEvent {
  189. type: ElementEventName,
  190. event: ZRRawEvent,
  191. // target can only be an element that is not silent.
  192. target: Element,
  193. // topTarget can be a silent element.
  194. topTarget: Element,
  195. cancelBubble: boolean,
  196. offsetX: number,
  197. offsetY: number,
  198. gestureEvent: string,
  199. pinchX: number,
  200. pinchY: number,
  201. pinchScale: number,
  202. wheelDelta: number,
  203. zrByTouch: boolean,
  204. which: number,
  205. stop: (this: ElementEvent) => void
  206. }
  207. export type ElementEventCallback<Ctx, Impl> = (
  208. this: CbThis<Ctx, Impl>, e: ElementEvent
  209. ) => boolean | void
  210. type CbThis<Ctx, Impl> = unknown extends Ctx ? Impl : Ctx;
  211. interface ElementEventHandlerProps {
  212. // Events
  213. onclick: ElementEventCallback<unknown, unknown>
  214. ondblclick: ElementEventCallback<unknown, unknown>
  215. onmouseover: ElementEventCallback<unknown, unknown>
  216. onmouseout: ElementEventCallback<unknown, unknown>
  217. onmousemove: ElementEventCallback<unknown, unknown>
  218. onmousewheel: ElementEventCallback<unknown, unknown>
  219. onmousedown: ElementEventCallback<unknown, unknown>
  220. onmouseup: ElementEventCallback<unknown, unknown>
  221. oncontextmenu: ElementEventCallback<unknown, unknown>
  222. ondrag: ElementEventCallback<unknown, unknown>
  223. ondragstart: ElementEventCallback<unknown, unknown>
  224. ondragend: ElementEventCallback<unknown, unknown>
  225. ondragenter: ElementEventCallback<unknown, unknown>
  226. ondragleave: ElementEventCallback<unknown, unknown>
  227. ondragover: ElementEventCallback<unknown, unknown>
  228. ondrop: ElementEventCallback<unknown, unknown>
  229. }
  230. export interface ElementProps extends Partial<ElementEventHandlerProps>, Partial<Pick<Transformable, TransformProp>> {
  231. name?: string
  232. ignore?: boolean
  233. isGroup?: boolean
  234. draggable?: boolean | 'horizontal' | 'vertical'
  235. silent?: boolean
  236. ignoreHostSilent?: boolean
  237. ignoreClip?: boolean
  238. globalScaleRatio?: number
  239. textConfig?: ElementTextConfig
  240. textContent?: ZRText
  241. clipPath?: Path
  242. drift?: Element['drift']
  243. extra?: Dictionary<unknown>
  244. // For echarts animation.
  245. anid?: string
  246. }
  247. // Properties can be used in state.
  248. export const PRESERVED_NORMAL_STATE = '__zr_normal__';
  249. // export const PRESERVED_MERGED_STATE = '__zr_merged__';
  250. const PRIMARY_STATES_KEYS = (TRANSFORMABLE_PROPS as any).concat(['ignore']) as [TransformProp, 'ignore'];
  251. const DEFAULT_ANIMATABLE_MAP = reduce(TRANSFORMABLE_PROPS, (obj, key) => {
  252. obj[key] = true;
  253. return obj;
  254. }, {ignore: false} as Partial<Record<ElementStatePropNames, boolean>>);
  255. export type ElementStatePropNames = (typeof PRIMARY_STATES_KEYS)[number] | 'textConfig';
  256. export type ElementState = Pick<ElementProps, ElementStatePropNames> & ElementCommonState
  257. export type ElementCommonState = {
  258. /**
  259. * NOTICE: Only canvas renderer supports hover layer. Users must not set hoverLayer
  260. * flag in non-canvas renderer, otherwise it may cause unexpected behavior.
  261. *
  262. * A truthy value (regardless of number or boolean) means hover layer is used.
  263. */
  264. hoverLayer?: boolean | number
  265. }
  266. export type ElementCalculateTextPosition = (
  267. out: TextPositionCalculationResult,
  268. style: ElementTextConfig,
  269. rect: RectLike
  270. ) => TextPositionCalculationResult;
  271. const tmpTextPosCalcRes = {} as TextPositionCalculationResult;
  272. const tmpBoundingRect = new BoundingRect(0, 0, 0, 0);
  273. const tmpInnerTextTrans: number[] = [];
  274. // It indicates a status of the element - whether it should be rendered or have been rendered
  275. // in a hover layer.
  276. // It also record the restriction of props changes when entering the hover status.
  277. // A falsy value means not in haver layer; a truthy value means in haver layer.
  278. export type InHoverLayerKind =
  279. typeof IN_HOVER_LAYER_KIND_NO
  280. | typeof IN_HOVER_LAYER_KIND_ONLY_STYLE_CHANGE;
  281. // | typeof IN_HOVER_LAYER_KIND_NO_LIMIT;
  282. // Not in hover layer.
  283. export const IN_HOVER_LAYER_KIND_NO = 0;
  284. // In hover layer and only style change when entering hover layer.
  285. export const IN_HOVER_LAYER_KIND_ONLY_STYLE_CHANGE = 1;
  286. // In hover layer and no restriction of changing.
  287. // export const IN_HOVER_LAYER_KIND_NO_LIMIT = 2;
  288. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  289. interface Element<Props extends ElementProps = ElementProps> extends Transformable,
  290. Eventful<{
  291. [key in ElementEventName]: (e: ElementEvent) => void | boolean
  292. } & {
  293. [key in string]: (...args: any) => void | boolean
  294. }>,
  295. ElementEventHandlerProps {
  296. }
  297. class Element<Props extends ElementProps = ElementProps> {
  298. id: number = guid()
  299. /**
  300. * Element type
  301. */
  302. type: string
  303. /**
  304. * Element name
  305. */
  306. name: string
  307. /**
  308. * If ignore drawing and events of the element object
  309. */
  310. ignore: boolean
  311. /**
  312. * Whether to respond to mouse events.
  313. */
  314. silent: boolean
  315. /**
  316. * When this element has `__hostTarget` (e.g., this is a `textContent`), whether
  317. * its silent is controlled by that host silent. They may need separate silent
  318. * settings. e.g., the host do not have `fill` but only `stroke`, or their mouse
  319. * events serve for different features.
  320. */
  321. ignoreHostSilent: boolean
  322. /**
  323. * 是否是 Group
  324. */
  325. isGroup: boolean
  326. /**
  327. * Whether it can be dragged.
  328. */
  329. draggable: boolean | 'horizontal' | 'vertical'
  330. /**
  331. * Whether is it dragging.
  332. */
  333. dragging: boolean
  334. parent: Group
  335. animators: Animator<any>[] = []
  336. /**
  337. * If ignore clip from it's parent or hosts.
  338. * Applied on itself and all it's children.
  339. *
  340. * NOTE: It won't affect the clipPath set on the children.
  341. */
  342. ignoreClip: boolean
  343. /**
  344. * If element is used as a component of other element.
  345. */
  346. __hostTarget: Element
  347. /**
  348. * ZRender instance will be assigned when element is associated with zrender
  349. */
  350. __zr: ZRenderType
  351. /**
  352. * Dirty bits.
  353. * From which painter will determine if this displayable object needs brush.
  354. */
  355. __dirty: number
  356. /**
  357. * If element was painted on the screen
  358. */
  359. __isRendered: boolean;
  360. /**
  361. * Whether this element has been moved to the hover layer.
  362. * If so, dirty will only trigger the zrender refresh hover layer.
  363. *
  364. * Hover layer is typically useful for progressive rendering case,
  365. * where the underlying layers can remain not dirty for most hovering
  366. * interactions.
  367. *
  368. * [HOVER_LAYER_CONSTRAINTS]:
  369. *
  370. * The "hover layer" mechanism expects the changes are applied only on a hover layer, while the original
  371. * layer should not be repainted. However, subsequent user operations may still require the original layer
  372. * to be repainted. If the element props have been modified due to hover state switching, the final effect
  373. * will differ unexpectedly after repainting.
  374. * For example, suppose a hover state defines different opacity, color and transform scale. when hovering
  375. * triggers that state, an extra glyph with those props is rendered on the hover layer and overlays the
  376. * the original glyph, but the original layer remains unchanged. The final effect is a visual composition
  377. * of the two. Then if clicking something to trigger a repaint of all layers (e.g., click echarts legend
  378. * to hide and then show them, or triggered by axisPointer, where hover style is expected to keep displaying),
  379. * and if it is rendered on the normal layer differently, the final composition is changed unexpectedly.
  380. *
  381. * Several candidate approches may resolve this issue:
  382. * (A) Clone elements for hover layer rendering. This might be a thorough solution, since all of the original
  383. * elements remain intact and can be repainted to the original layer without changes.
  384. * (B) Introduce a separate `__hoverStyle` to keep the original `this.style` unchanged, and only styles
  385. * changes are allowed in entering or leaving hover layer via `useState` and `useStates` while other changes
  386. * are ignored. And for simplicity, and no separate structures are provided for storing other props.
  387. * This approach can resolve many cases, but is still problematic in some cases.
  388. *
  389. * PENDING:
  390. * 1. Currently we simply implement (B), until some concrete scenarios require (A) in future.
  391. * 2. [HOVER_LAYER_CONSTRAINTS_TEXT]
  392. * Consider:
  393. * - Text style change may lead to creating or updating of subText elements (TSpan).
  394. * - An special handling can be make in (B) - if the element is not rendered on the original layer
  395. * (typically due to `ignore: true` or `invisible: true`), it can be rendered to the hover layer
  396. * without the restriction "only style can change". This is useful to the scenario "hover an
  397. * element to show its _textContent".
  398. * All these cases require display list to be re-built, or need a exclusive display list for hover layer,
  399. * and more precise dirty bit (REDRAW_BIT) handling is needed for that.
  400. * But it would introduce considerable complexity. And unlike `Path`, rendering the same text in multiple
  401. * layers may cause undesirable visual effect. Therefore, we do not implement it unless required. Currently
  402. * hover layer is disabled for text. Text must still be rendered, since it may carry important infomation.
  403. */
  404. __inHover: InHoverLayerKind
  405. __clipPaths?: Path[]
  406. /**
  407. * path to clip the elements and its children, if it is a group.
  408. * @see http://www.w3.org/TR/2dcontext/#clipping-region
  409. */
  410. private _clipPath?: Path
  411. /**
  412. * Attached text element.
  413. * `position`, `style.textAlign`, `style.textVerticalAlign`
  414. * of element will be ignored if textContent.position is set
  415. */
  416. private _textContent?: ZRText
  417. /**
  418. * Text guide line.
  419. */
  420. private _textGuide?: Polyline
  421. /**
  422. * Config of textContent. Inlcuding layout, color, ...etc.
  423. */
  424. textConfig?: ElementTextConfig
  425. /**
  426. * Config for guide line calculating.
  427. *
  428. * NOTE: This is just a property signature. READ and WRITE are all done in echarts.
  429. */
  430. textGuideLineConfig?: ElementTextGuideLineConfig
  431. // FOR ECHARTS
  432. /**
  433. * Id for mapping animation
  434. */
  435. anid: string
  436. extra: Dictionary<unknown>
  437. currentStates?: string[] = []
  438. // prevStates is for storager in echarts.
  439. prevStates?: string[]
  440. /**
  441. * Store of element state.
  442. * '__normal__' key is preserved for default properties.
  443. */
  444. states: Dictionary<ElementState> = {}
  445. /**
  446. * Animation config applied on state switching.
  447. */
  448. stateTransition: ElementAnimateConfig
  449. /**
  450. * Proxy function for getting state with given stateName.
  451. * ZRender will first try to get with stateProxy. Then find from states if stateProxy returns nothing
  452. *
  453. * targetStates will be given in useStates
  454. */
  455. stateProxy?: (stateName: string, targetStates?: string[]) => ElementState
  456. protected _normalState: ElementState
  457. // Temporary storage for inside text color configuration.
  458. private _innerTextDefaultStyle: DefaultTextStyle
  459. constructor(props?: Props) {
  460. this._init(props);
  461. }
  462. protected _init(props?: Props) {
  463. // Init default properties
  464. this.attr(props);
  465. }
  466. /**
  467. * Drift element
  468. * @param {number} dx dx on the global space
  469. * @param {number} dy dy on the global space
  470. */
  471. drift(dx: number, dy: number, e?: ElementEvent) {
  472. switch (this.draggable) {
  473. case 'horizontal':
  474. dy = 0;
  475. break;
  476. case 'vertical':
  477. dx = 0;
  478. break;
  479. }
  480. let m = this.transform;
  481. if (!m) {
  482. m = this.transform = [1, 0, 0, 1, 0, 0];
  483. }
  484. m[4] += dx;
  485. m[5] += dy;
  486. this.decomposeTransform();
  487. this.markRedraw();
  488. }
  489. /**
  490. * Hook before update
  491. */
  492. beforeUpdate() {}
  493. /**
  494. * Hook after update
  495. */
  496. afterUpdate() {}
  497. /**
  498. * Update each frame
  499. */
  500. update() {
  501. this.updateTransform();
  502. if (this.__dirty) {
  503. this.updateInnerText();
  504. }
  505. }
  506. updateInnerText(forceUpdate?: boolean) {
  507. // Update textContent
  508. const textEl = this._textContent;
  509. if (textEl && (!textEl.ignore || forceUpdate)) {
  510. if (!this.textConfig) {
  511. this.textConfig = {};
  512. }
  513. const textConfig = this.textConfig;
  514. const isLocal = textConfig.local;
  515. const innerTransformable = textEl.innerTransformable;
  516. let textAlign: TextAlign;
  517. let textVerticalAlign: TextVerticalAlign;
  518. let textStyleChanged = false;
  519. // Apply host's transform.
  520. innerTransformable.parent = isLocal ? this as unknown as Group : null;
  521. let innerOrigin = false;
  522. // Reset x/y/rotation
  523. innerTransformable.copyTransform(textEl);
  524. const hasPosition = textConfig.position != null;
  525. const autoOverflowArea = textConfig.autoOverflowArea;
  526. let layoutRect: BoundingRect;
  527. if (autoOverflowArea || hasPosition) {
  528. layoutRect = tmpBoundingRect;
  529. if (textConfig.layoutRect) {
  530. layoutRect.copy(textConfig.layoutRect);
  531. }
  532. else {
  533. layoutRect.copy(this.getBoundingRect());
  534. }
  535. if (!isLocal) {
  536. layoutRect.applyTransform(this.transform);
  537. }
  538. }
  539. // Force set attached text's position if `position` is in config.
  540. if (hasPosition) {
  541. if (this.calculateTextPosition) {
  542. this.calculateTextPosition(tmpTextPosCalcRes, textConfig, layoutRect);
  543. }
  544. else {
  545. calculateTextPosition(tmpTextPosCalcRes, textConfig, layoutRect);
  546. }
  547. // TODO Should modify back if textConfig.position is set to null again.
  548. // Or textContent is detached.
  549. innerTransformable.x = tmpTextPosCalcRes.x;
  550. innerTransformable.y = tmpTextPosCalcRes.y;
  551. // User specified align/verticalAlign has higher priority, which is
  552. // useful in the case that attached text is rotated 90 degree.
  553. textAlign = tmpTextPosCalcRes.align;
  554. textVerticalAlign = tmpTextPosCalcRes.verticalAlign;
  555. const textOrigin = textConfig.origin;
  556. if (textOrigin && textConfig.rotation != null) {
  557. let relOriginX;
  558. let relOriginY;
  559. if (textOrigin === 'center') {
  560. relOriginX = layoutRect.width * 0.5;
  561. relOriginY = layoutRect.height * 0.5;
  562. }
  563. else {
  564. relOriginX = parsePercent(textOrigin[0], layoutRect.width);
  565. relOriginY = parsePercent(textOrigin[1], layoutRect.height);
  566. }
  567. innerOrigin = true;
  568. innerTransformable.originX = -innerTransformable.x + relOriginX + (isLocal ? 0 : layoutRect.x);
  569. innerTransformable.originY = -innerTransformable.y + relOriginY + (isLocal ? 0 : layoutRect.y);
  570. }
  571. }
  572. if (textConfig.rotation != null) {
  573. innerTransformable.rotation = textConfig.rotation;
  574. }
  575. // TODO
  576. const textOffset = textConfig.offset;
  577. if (textOffset) {
  578. innerTransformable.x += textOffset[0];
  579. innerTransformable.y += textOffset[1];
  580. // Not change the user set origin.
  581. if (!innerOrigin) {
  582. innerTransformable.originX = -textOffset[0];
  583. innerTransformable.originY = -textOffset[1];
  584. }
  585. }
  586. const innerTextDefaultStyle = this._innerTextDefaultStyle || (this._innerTextDefaultStyle = {});
  587. if (autoOverflowArea) {
  588. const overflowRect = innerTextDefaultStyle.overflowRect =
  589. innerTextDefaultStyle.overflowRect || new BoundingRect(0, 0, 0, 0);
  590. innerTransformable.getLocalTransform(tmpInnerTextTrans);
  591. invert(tmpInnerTextTrans, tmpInnerTextTrans);
  592. BoundingRect.copy(overflowRect, layoutRect);
  593. // If transform to a non-orthogonal state (e.g. rotate PI/3), the result of this "apply"
  594. // is not expected. But we don't need to address it until a real scenario arises.
  595. overflowRect.applyTransform(tmpInnerTextTrans);
  596. }
  597. else {
  598. innerTextDefaultStyle.overflowRect = null;
  599. }
  600. // [CAUTION] Do not change `innerTransformable` below.
  601. // Calculate text color
  602. const isInside = textConfig.inside == null // Force to be inside or not.
  603. ? (typeof textConfig.position === 'string' && textConfig.position.indexOf('inside') >= 0)
  604. : textConfig.inside;
  605. let textFill;
  606. let textStroke;
  607. let autoStroke;
  608. if (isInside && this.canBeInsideText()) {
  609. // In most cases `textContent` need this "auto" strategy.
  610. // So by default be 'auto'. Otherwise users need to literally
  611. // set `insideFill: 'auto', insideStroke: 'auto'` each time.
  612. textFill = textConfig.insideFill;
  613. textStroke = textConfig.insideStroke;
  614. if (textFill == null || textFill === 'auto') {
  615. textFill = this.getInsideTextFill();
  616. }
  617. if (textStroke == null || textStroke === 'auto') {
  618. textStroke = this.getInsideTextStroke(textFill);
  619. autoStroke = true;
  620. }
  621. }
  622. else {
  623. textFill = textConfig.outsideFill;
  624. textStroke = textConfig.outsideStroke;
  625. if (textFill == null || textFill === 'auto') {
  626. textFill = this.getOutsideFill();
  627. }
  628. // By default give a stroke to distinguish "front end" label with
  629. // messy background (like other text label, line or other graphic).
  630. // If textContent.style.fill specified, this auto stroke will not be used.
  631. if (textStroke == null || textStroke === 'auto') {
  632. // If some time need to customize the default stroke getter,
  633. // add some kind of override method.
  634. textStroke = this.getOutsideStroke(textFill);
  635. autoStroke = true;
  636. }
  637. }
  638. // Default `textFill` should must have a value to ensure text can be displayed.
  639. textFill = textFill || '#000';
  640. if (textFill !== innerTextDefaultStyle.fill
  641. || textStroke !== innerTextDefaultStyle.stroke
  642. || autoStroke !== innerTextDefaultStyle.autoStroke
  643. || textAlign !== innerTextDefaultStyle.align
  644. || textVerticalAlign !== innerTextDefaultStyle.verticalAlign
  645. ) {
  646. textStyleChanged = true;
  647. innerTextDefaultStyle.fill = textFill;
  648. innerTextDefaultStyle.stroke = textStroke;
  649. innerTextDefaultStyle.autoStroke = autoStroke;
  650. innerTextDefaultStyle.align = textAlign;
  651. innerTextDefaultStyle.verticalAlign = textVerticalAlign;
  652. textEl.setDefaultTextStyle(innerTextDefaultStyle);
  653. }
  654. // Mark textEl to update transform.
  655. // DON'T use markRedraw. It will cause Element itself to dirty again.
  656. textEl.__dirty |= REDRAW_BIT;
  657. if (textStyleChanged) {
  658. // Only mark style dirty if necessary. Update ZRText is costly.
  659. textEl.dirtyStyle(true);
  660. }
  661. }
  662. }
  663. protected canBeInsideText() {
  664. return true;
  665. }
  666. protected getInsideTextFill(): string | undefined {
  667. return '#fff';
  668. }
  669. protected getInsideTextStroke(textFill: string): string | undefined {
  670. return '#000';
  671. }
  672. protected getOutsideFill(): string | undefined {
  673. return this.__zr && this.__zr.isDarkMode() ? LIGHT_LABEL_COLOR : DARK_LABEL_COLOR;
  674. }
  675. protected getOutsideStroke(textFill: string): string {
  676. const backgroundColor = this.__zr && this.__zr.getBackgroundColor();
  677. let colorArr = typeof backgroundColor === 'string' && parse(backgroundColor as string);
  678. if (!colorArr) {
  679. colorArr = [255, 255, 255, 1];
  680. }
  681. // Assume blending on a white / black(dark) background.
  682. const alpha = colorArr[3];
  683. const isDark = this.__zr.isDarkMode();
  684. for (let i = 0; i < 3; i++) {
  685. colorArr[i] = colorArr[i] * alpha + (isDark ? 0 : 255) * (1 - alpha);
  686. }
  687. colorArr[3] = 1;
  688. return stringify(colorArr, 'rgba');
  689. }
  690. traverse<Context>(
  691. cb: (this: Context, el: Element<Props>) => void,
  692. context?: Context
  693. ) {}
  694. protected attrKV(key: string, value: unknown) {
  695. if (key === 'textConfig') {
  696. this.setTextConfig(value as ElementTextConfig);
  697. }
  698. else if (key === 'textContent') {
  699. this.setTextContent(value as ZRText);
  700. }
  701. else if (key === 'clipPath') {
  702. this.setClipPath(value as Path);
  703. }
  704. else if (key === 'extra') {
  705. this.extra = this.extra || {};
  706. extend(this.extra, value);
  707. }
  708. else {
  709. (this as any)[key] = value;
  710. }
  711. }
  712. /**
  713. * Hide the element
  714. */
  715. hide() {
  716. this.ignore = true;
  717. this.markRedraw();
  718. }
  719. /**
  720. * Show the element
  721. */
  722. show() {
  723. this.ignore = false;
  724. this.markRedraw();
  725. }
  726. attr(keyOrObj: Props): this
  727. attr<T extends keyof Props>(keyOrObj: T, value: Props[T]): this
  728. attr(keyOrObj: keyof Props | Props, value?: unknown): this {
  729. if (typeof keyOrObj === 'string') {
  730. this.attrKV(keyOrObj as keyof ElementProps, value as AllPropTypes<ElementProps>);
  731. }
  732. else if (isObject(keyOrObj)) {
  733. let obj = keyOrObj as object;
  734. let keysArr = keys(obj);
  735. for (let i = 0; i < keysArr.length; i++) {
  736. let key = keysArr[i];
  737. this.attrKV(key as keyof ElementProps, keyOrObj[key]);
  738. }
  739. }
  740. this.markRedraw();
  741. return this;
  742. }
  743. // Save current state to normal
  744. saveCurrentToNormalState(toState: ElementState) {
  745. this._innerSaveToNormal(toState);
  746. // If we are switching from normal to other state during animation.
  747. // We need to save final value of animation to the normal state. Not interpolated value.
  748. const normalState = this._normalState;
  749. for (let i = 0; i < this.animators.length; i++) {
  750. const animator = this.animators[i];
  751. const fromStateTransition = animator.__fromStateTransition;
  752. // Ignore animation from state transition(except normal).
  753. // Ignore loop animation.
  754. if (animator.getLoop() || fromStateTransition && fromStateTransition !== PRESERVED_NORMAL_STATE) {
  755. continue;
  756. }
  757. const targetName = animator.targetName;
  758. // Respecting the order of animation if multiple animator is
  759. // animating on the same property(If additive animation is used)
  760. const target = targetName
  761. ? (normalState as any)[targetName] : normalState;
  762. // Only save keys that are changed by the states.
  763. animator.saveTo(target);
  764. }
  765. }
  766. protected _innerSaveToNormal(toState: ElementState) {
  767. let normalState = this._normalState;
  768. if (!normalState) {
  769. // Clear previous stored normal states when switching from normalState to otherState.
  770. normalState = this._normalState = {};
  771. }
  772. if (toState.textConfig && !normalState.textConfig) {
  773. normalState.textConfig = this.textConfig;
  774. }
  775. this._savePrimaryToNormal(toState, normalState, PRIMARY_STATES_KEYS);
  776. }
  777. protected _savePrimaryToNormal(
  778. toState: Dictionary<any>, normalState: Dictionary<any>, primaryKeys: readonly string[]
  779. ) {
  780. for (let i = 0; i < primaryKeys.length; i++) {
  781. let key = primaryKeys[i];
  782. // Only save property that will be changed by toState
  783. // and has not been saved to normalState yet.
  784. if (toState[key] != null && !(key in normalState)) {
  785. (normalState as any)[key] = (this as any)[key];
  786. }
  787. }
  788. }
  789. /**
  790. * If has any state.
  791. */
  792. hasState() {
  793. return this.currentStates.length > 0;
  794. }
  795. /**
  796. * Get state object
  797. */
  798. getState(name: string) {
  799. return this.states[name];
  800. }
  801. /**
  802. * Ensure state exists. If not, will create one and return.
  803. */
  804. ensureState(name: string) {
  805. const states = this.states;
  806. if (!states[name]) {
  807. states[name] = {};
  808. }
  809. return states[name];
  810. }
  811. /**
  812. * Clear all states.
  813. */
  814. clearStates(noAnimation?: boolean) {
  815. this.useState(PRESERVED_NORMAL_STATE, false, noAnimation);
  816. // TODO set _normalState to null?
  817. }
  818. /**
  819. * Use state. State is a collection of properties.
  820. * Will return current state object if state exists and stateName has been changed.
  821. *
  822. * @param stateName State name to be switched to
  823. * @param keepCurrentState If keep current states.
  824. * If not, it will inherit from the normal state.
  825. */
  826. useState(stateName: string, keepCurrentStates?: boolean, noAnimation?: boolean, forceUseHoverLayer?: boolean) {
  827. // Use preserved word __normal__
  828. // TODO: Only restore changed properties when restore to normal???
  829. const toNormalState = stateName === PRESERVED_NORMAL_STATE;
  830. const hasStates = this.hasState();
  831. if (!hasStates && toNormalState) {
  832. // If switched from normal to normal.
  833. return;
  834. }
  835. const currentStates = this.currentStates;
  836. const animationCfg = this.stateTransition;
  837. // No need to change in following cases:
  838. // 1. Keep current states. and already being applied before.
  839. // 2. Don't keep current states. And new state is same with the only one exists state.
  840. if (indexOf(currentStates, stateName) >= 0 && (keepCurrentStates || currentStates.length === 1)) {
  841. return;
  842. }
  843. let state;
  844. if (this.stateProxy && !toNormalState) {
  845. state = this.stateProxy(stateName);
  846. }
  847. if (!state) {
  848. state = (this.states && this.states[stateName]);
  849. }
  850. if (!state && !toNormalState) {
  851. logError(`State ${stateName} not exists.`);
  852. return;
  853. }
  854. if (!toNormalState) {
  855. this.saveCurrentToNormalState(state);
  856. }
  857. const textContent = this._textContent;
  858. const useHoverLayer = shouldUseHoverLayer(this, textContent, state, forceUseHoverLayer);
  859. if (useHoverLayer && !this.__inHover) {
  860. // Enter hover layer before states update.
  861. this.__inHover = useHoverLayer;
  862. }
  863. this._applyStateObj(
  864. stateName,
  865. state,
  866. this._normalState,
  867. keepCurrentStates,
  868. canTransition(this, noAnimation, animationCfg),
  869. animationCfg,
  870. );
  871. // Also set text content.
  872. const textGuide = this._textGuide;
  873. if (textContent) {
  874. // Force textContent use hover layer if self is using it.
  875. textContent.useState(stateName, keepCurrentStates, noAnimation, !!useHoverLayer);
  876. }
  877. if (textGuide) {
  878. textGuide.useState(stateName, keepCurrentStates, noAnimation, !!useHoverLayer);
  879. }
  880. if (toNormalState) {
  881. // Clear state
  882. this.currentStates = [];
  883. // Reset normal state.
  884. this._normalState = {};
  885. }
  886. else {
  887. if (!keepCurrentStates) {
  888. this.currentStates = [stateName];
  889. }
  890. else {
  891. this.currentStates.push(stateName);
  892. }
  893. }
  894. // Update animating target to the new object after state changed.
  895. this._updateAnimationTargets();
  896. this.markRedraw();
  897. if (!useHoverLayer && this.__inHover) {
  898. // Leave hover layer after states update and markRedraw.
  899. this.__inHover = IN_HOVER_LAYER_KIND_NO;
  900. // NOTE: avoid unexpected refresh when moving out from hover layer!!
  901. // Only clear from hover layer.
  902. this.__dirty &= ~REDRAW_BIT;
  903. }
  904. // Return used state.
  905. return state;
  906. }
  907. /**
  908. * Apply multiple states.
  909. * @param states States list.
  910. */
  911. useStates(states: string[], noAnimation?: boolean, forceUseHoverLayer?: boolean) {
  912. if (!states.length) {
  913. this.clearStates();
  914. }
  915. else {
  916. const stateObjects: ElementState[] = [];
  917. const currentStates = this.currentStates;
  918. const len = states.length;
  919. let notChange = len === currentStates.length;
  920. if (notChange) {
  921. for (let i = 0; i < len; i++) {
  922. if (states[i] !== currentStates[i]) {
  923. notChange = false;
  924. break;
  925. }
  926. }
  927. }
  928. if (notChange) {
  929. return;
  930. }
  931. for (let i = 0; i < len; i++) {
  932. const stateName = states[i];
  933. let stateObj: ElementState;
  934. if (this.stateProxy) {
  935. stateObj = this.stateProxy(stateName, states);
  936. }
  937. if (!stateObj) {
  938. stateObj = this.states[stateName];
  939. }
  940. if (stateObj) {
  941. stateObjects.push(stateObj);
  942. }
  943. }
  944. const lastStateObj = stateObjects[len - 1];
  945. const textContent = this._textContent;
  946. const useHoverLayer = shouldUseHoverLayer(this, textContent, lastStateObj, forceUseHoverLayer);
  947. if (useHoverLayer && !this.__inHover) {
  948. // Enter hover layer before states update.
  949. this.__inHover = useHoverLayer;
  950. }
  951. const mergedState = this._mergeStates(stateObjects);
  952. const animationCfg = this.stateTransition;
  953. this.saveCurrentToNormalState(mergedState);
  954. this._applyStateObj(
  955. states.join(','),
  956. mergedState,
  957. this._normalState,
  958. false,
  959. canTransition(this, noAnimation, animationCfg),
  960. animationCfg,
  961. );
  962. const textGuide = this._textGuide;
  963. if (textContent) {
  964. textContent.useStates(states, noAnimation, !!useHoverLayer);
  965. }
  966. if (textGuide) {
  967. textGuide.useStates(states, noAnimation, !!useHoverLayer);
  968. }
  969. this._updateAnimationTargets();
  970. // Create a copy
  971. this.currentStates = states.slice();
  972. this.markRedraw();
  973. if (!useHoverLayer && this.__inHover) {
  974. // Leave hover layer after states update and markRedraw.
  975. this.__inHover = IN_HOVER_LAYER_KIND_NO;
  976. // NOTE: avoid unexpected refresh when moving out from hover layer!!
  977. // Only clear from hover layer.
  978. this.__dirty &= ~REDRAW_BIT;
  979. }
  980. }
  981. }
  982. /**
  983. * Return if el.silent or any ancestor element has silent true.
  984. */
  985. isSilent() {
  986. // Follow the logic of `Handler.ts`#`isHover`.
  987. let el: Element = this;
  988. while (el) {
  989. if (el.silent) {
  990. return true;
  991. }
  992. const hostEl = el.__hostTarget;
  993. el = hostEl ? (el.ignoreHostSilent ? null : hostEl) : el.parent;
  994. }
  995. return false;
  996. }
  997. /**
  998. * Update animation targets when reference is changed.
  999. */
  1000. private _updateAnimationTargets() {
  1001. for (let i = 0; i < this.animators.length; i++) {
  1002. const animator = this.animators[i];
  1003. if (animator.targetName) {
  1004. animator.changeTarget((this as any)[animator.targetName]);
  1005. }
  1006. }
  1007. }
  1008. /**
  1009. * Remove state
  1010. * @param state State to remove
  1011. */
  1012. removeState(state: string) {
  1013. const idx = indexOf(this.currentStates, state);
  1014. if (idx >= 0) {
  1015. const currentStates = this.currentStates.slice();
  1016. currentStates.splice(idx, 1);
  1017. this.useStates(currentStates);
  1018. }
  1019. }
  1020. /**
  1021. * Replace exists state.
  1022. * @param oldState
  1023. * @param newState
  1024. * @param forceAdd If still add when even if replaced target not exists.
  1025. */
  1026. replaceState(oldState: string, newState: string, forceAdd: boolean) {
  1027. const currentStates = this.currentStates.slice();
  1028. const idx = indexOf(currentStates, oldState);
  1029. const newStateExists = indexOf(currentStates, newState) >= 0;
  1030. if (idx >= 0) {
  1031. if (!newStateExists) {
  1032. // Replace the old with the new one.
  1033. currentStates[idx] = newState;
  1034. }
  1035. else {
  1036. // Only remove the old one.
  1037. currentStates.splice(idx, 1);
  1038. }
  1039. }
  1040. else if (forceAdd && !newStateExists) {
  1041. currentStates.push(newState);
  1042. }
  1043. this.useStates(currentStates);
  1044. }
  1045. /**
  1046. * Toogle state.
  1047. */
  1048. toggleState(state: string, enable: boolean) {
  1049. if (enable) {
  1050. this.useState(state, true);
  1051. }
  1052. else {
  1053. this.removeState(state);
  1054. }
  1055. }
  1056. protected _mergeStates(states: ElementState[]) {
  1057. const mergedState: ElementState = {};
  1058. let mergedTextConfig: ElementTextConfig;
  1059. for (let i = 0; i < states.length; i++) {
  1060. const state = states[i];
  1061. extend(mergedState, state);
  1062. if (state.textConfig) {
  1063. mergedTextConfig = mergedTextConfig || {};
  1064. extend(mergedTextConfig, state.textConfig);
  1065. }
  1066. }
  1067. if (mergedTextConfig) {
  1068. mergedState.textConfig = mergedTextConfig;
  1069. }
  1070. return mergedState;
  1071. }
  1072. protected _applyStateObj(
  1073. stateName: string,
  1074. state: ElementState,
  1075. normalState: ElementState,
  1076. keepCurrentStates: boolean,
  1077. transition: boolean,
  1078. animationCfg: ElementAnimateConfig
  1079. ) {
  1080. if (this.__inHover === IN_HOVER_LAYER_KIND_ONLY_STYLE_CHANGE) {
  1081. return;
  1082. }
  1083. const needsRestoreToNormal = !(state && keepCurrentStates);
  1084. // TODO: Save current state to normal?
  1085. // TODO: Animation
  1086. if (state && state.textConfig) {
  1087. // Inherit from current state or normal state.
  1088. this.textConfig = extend(
  1089. {},
  1090. keepCurrentStates ? this.textConfig : normalState.textConfig
  1091. );
  1092. extend(this.textConfig, state.textConfig);
  1093. }
  1094. else if (needsRestoreToNormal) {
  1095. if (normalState.textConfig) { // Only restore if changed and saved.
  1096. this.textConfig = normalState.textConfig;
  1097. }
  1098. }
  1099. const transitionTarget: Dictionary<any> = {};
  1100. let hasTransition = false;
  1101. for (let i = 0; i < PRIMARY_STATES_KEYS.length; i++) {
  1102. const key = PRIMARY_STATES_KEYS[i];
  1103. const propNeedsTransition = transition && DEFAULT_ANIMATABLE_MAP[key];
  1104. if (state && state[key] != null) {
  1105. if (propNeedsTransition) {
  1106. hasTransition = true;
  1107. transitionTarget[key] = state[key];
  1108. }
  1109. else {
  1110. // Replace if it exist in target state
  1111. (this as any)[key] = state[key];
  1112. }
  1113. }
  1114. else if (needsRestoreToNormal) {
  1115. if (normalState[key] != null) {
  1116. if (propNeedsTransition) {
  1117. hasTransition = true;
  1118. transitionTarget[key] = normalState[key];
  1119. }
  1120. else {
  1121. // Restore to normal state
  1122. (this as any)[key] = normalState[key];
  1123. }
  1124. }
  1125. }
  1126. }
  1127. if (!transition) {
  1128. // Keep the running animation to the new values after states changed.
  1129. // Not simply stop animation. Or it may have jump effect.
  1130. for (let i = 0; i < this.animators.length; i++) {
  1131. const animator = this.animators[i];
  1132. const targetName = animator.targetName;
  1133. // Ignore loop animation
  1134. if (!animator.getLoop()) {
  1135. animator.__changeFinalValue(targetName
  1136. ? ((state || normalState) as any)[targetName]
  1137. : (state || normalState)
  1138. );
  1139. }
  1140. }
  1141. }
  1142. if (hasTransition) {
  1143. this._transitionState(
  1144. stateName,
  1145. transitionTarget as Props,
  1146. animationCfg
  1147. );
  1148. }
  1149. }
  1150. /**
  1151. * Component is some elements attached on this element for specific purpose.
  1152. * Like clipPath, textContent
  1153. */
  1154. private _attachComponent(componentEl: Element) {
  1155. if (componentEl.__zr && !componentEl.__hostTarget) {
  1156. if (process.env.NODE_ENV !== 'production') {
  1157. throw new Error('Text element has been added to zrender.');
  1158. }
  1159. return;
  1160. }
  1161. if (componentEl === this) {
  1162. if (process.env.NODE_ENV !== 'production') {
  1163. throw new Error('Recursive component attachment.');
  1164. }
  1165. return;
  1166. }
  1167. const zr = this.__zr;
  1168. if (zr) {
  1169. // Needs to add self to zrender. For rerender triggering, or animation.
  1170. componentEl.addSelfToZr(zr);
  1171. }
  1172. componentEl.__zr = zr;
  1173. componentEl.__hostTarget = this as unknown as Element;
  1174. }
  1175. private _detachComponent(componentEl: Element) {
  1176. if (componentEl.__zr) {
  1177. componentEl.removeSelfFromZr(componentEl.__zr);
  1178. }
  1179. componentEl.__zr = null;
  1180. componentEl.__hostTarget = null;
  1181. }
  1182. /**
  1183. * Get clip path
  1184. */
  1185. getClipPath() {
  1186. return this._clipPath;
  1187. }
  1188. /**
  1189. * Set clip path
  1190. *
  1191. * clipPath can't be shared between two elements.
  1192. */
  1193. setClipPath(clipPath: Path) {
  1194. // Remove previous clip path
  1195. if (this._clipPath && this._clipPath !== clipPath) {
  1196. this.removeClipPath();
  1197. }
  1198. this._attachComponent(clipPath);
  1199. this._clipPath = clipPath;
  1200. this.markRedraw();
  1201. }
  1202. /**
  1203. * Remove clip path
  1204. */
  1205. removeClipPath() {
  1206. const clipPath = this._clipPath;
  1207. if (clipPath) {
  1208. this._detachComponent(clipPath);
  1209. this._clipPath = null;
  1210. this.markRedraw();
  1211. }
  1212. }
  1213. /**
  1214. * Get attached text content.
  1215. */
  1216. getTextContent(): ZRText {
  1217. return this._textContent;
  1218. }
  1219. /**
  1220. * Attach text on element
  1221. */
  1222. setTextContent(textEl: ZRText) {
  1223. const previousTextContent = this._textContent;
  1224. if (previousTextContent === textEl) {
  1225. return;
  1226. }
  1227. // Remove previous textContent
  1228. if (previousTextContent && previousTextContent !== textEl) {
  1229. this.removeTextContent();
  1230. }
  1231. if (process.env.NODE_ENV !== 'production') {
  1232. if (textEl.__zr && !textEl.__hostTarget) {
  1233. throw new Error('Text element has been added to zrender.');
  1234. }
  1235. }
  1236. textEl.innerTransformable = new Transformable();
  1237. this._attachComponent(textEl);
  1238. this._textContent = textEl;
  1239. this.markRedraw();
  1240. }
  1241. /**
  1242. * Set layout of attached text. Will merge with the previous.
  1243. */
  1244. setTextConfig(cfg: ElementTextConfig) {
  1245. // TODO hide cfg property?
  1246. if (!this.textConfig) {
  1247. this.textConfig = {};
  1248. }
  1249. extend(this.textConfig, cfg);
  1250. this.markRedraw();
  1251. }
  1252. /**
  1253. * Remove text config
  1254. */
  1255. removeTextConfig() {
  1256. this.textConfig = null;
  1257. this.markRedraw();
  1258. }
  1259. /**
  1260. * Remove attached text element.
  1261. */
  1262. removeTextContent() {
  1263. const textEl = this._textContent;
  1264. if (textEl) {
  1265. textEl.innerTransformable = null;
  1266. this._detachComponent(textEl);
  1267. this._textContent = null;
  1268. this._innerTextDefaultStyle = null;
  1269. this.markRedraw();
  1270. }
  1271. }
  1272. getTextGuideLine(): Polyline {
  1273. return this._textGuide;
  1274. }
  1275. setTextGuideLine(guideLine: Polyline) {
  1276. // Remove previous clip path
  1277. if (this._textGuide && this._textGuide !== guideLine) {
  1278. this.removeTextGuideLine();
  1279. }
  1280. this._attachComponent(guideLine);
  1281. this._textGuide = guideLine;
  1282. this.markRedraw();
  1283. }
  1284. removeTextGuideLine() {
  1285. const textGuide = this._textGuide;
  1286. if (textGuide) {
  1287. this._detachComponent(textGuide);
  1288. this._textGuide = null;
  1289. this.markRedraw();
  1290. }
  1291. }
  1292. /**
  1293. * Mark element needs to be repainted
  1294. */
  1295. markRedraw() {
  1296. this.__dirty |= REDRAW_BIT;
  1297. const zr = this.__zr;
  1298. if (zr) {
  1299. if (this.__inHover) {
  1300. zr.refreshHover();
  1301. }
  1302. else {
  1303. zr.refresh();
  1304. }
  1305. }
  1306. // Used as a clipPath or textContent
  1307. if (this.__hostTarget) {
  1308. this.__hostTarget.markRedraw();
  1309. }
  1310. }
  1311. /**
  1312. * Besides marking elements to be refreshed.
  1313. * It will also invalid all cache and doing recalculate next frame.
  1314. */
  1315. dirty() {
  1316. this.markRedraw();
  1317. }
  1318. /**
  1319. * Add self from zrender instance.
  1320. * Not recursively because it will be invoked when element added to storage.
  1321. */
  1322. addSelfToZr(zr: ZRenderType) {
  1323. if (this.__zr === zr) {
  1324. return;
  1325. }
  1326. this.__zr = zr;
  1327. // 添加动画
  1328. const animators = this.animators;
  1329. if (animators) {
  1330. for (let i = 0; i < animators.length; i++) {
  1331. zr.animation.addAnimator(animators[i]);
  1332. }
  1333. }
  1334. if (this._clipPath) {
  1335. this._clipPath.addSelfToZr(zr);
  1336. }
  1337. if (this._textContent) {
  1338. this._textContent.addSelfToZr(zr);
  1339. }
  1340. if (this._textGuide) {
  1341. this._textGuide.addSelfToZr(zr);
  1342. }
  1343. }
  1344. /**
  1345. * Remove self from zrender instance.
  1346. * Not recursively because it will be invoked when element added to storage.
  1347. */
  1348. removeSelfFromZr(zr: ZRenderType) {
  1349. if (!this.__zr) {
  1350. return;
  1351. }
  1352. this.__zr = null;
  1353. // Remove animation
  1354. const animators = this.animators;
  1355. if (animators) {
  1356. for (let i = 0; i < animators.length; i++) {
  1357. zr.animation.removeAnimator(animators[i]);
  1358. }
  1359. }
  1360. if (this._clipPath) {
  1361. this._clipPath.removeSelfFromZr(zr);
  1362. }
  1363. if (this._textContent) {
  1364. this._textContent.removeSelfFromZr(zr);
  1365. }
  1366. if (this._textGuide) {
  1367. this._textGuide.removeSelfFromZr(zr);
  1368. }
  1369. }
  1370. /**
  1371. * 动画
  1372. *
  1373. * @param path The key to fetch value from object. Mostly style or shape.
  1374. * @param loop Whether to loop animation.
  1375. * @param allowDiscreteAnimation Whether to allow discrete animation
  1376. * @example:
  1377. * el.animate('style', false)
  1378. * .when(1000, {x: 10} )
  1379. * .done(function(){ // Animation done })
  1380. * .start()
  1381. */
  1382. animate(key?: string, loop?: boolean, allowDiscreteAnimation?: boolean) {
  1383. let target = key ? (this as any)[key] : this;
  1384. if (process.env.NODE_ENV !== 'production') {
  1385. if (!target) {
  1386. logError(
  1387. 'Property "'
  1388. + key
  1389. + '" is not existed in element '
  1390. + this.id
  1391. );
  1392. return;
  1393. }
  1394. }
  1395. const animator = new Animator(target, loop, allowDiscreteAnimation);
  1396. key && (animator.targetName = key);
  1397. this.addAnimator(animator, key);
  1398. return animator;
  1399. }
  1400. addAnimator(animator: Animator<any>, key: string): void {
  1401. const zr = this.__zr;
  1402. const el = this;
  1403. animator.during(function () {
  1404. el.updateDuringAnimation(key as string);
  1405. }).done(function () {
  1406. const animators = el.animators;
  1407. // FIXME Animator will not be removed if use `Animator#stop` to stop animation
  1408. const idx = indexOf(animators, animator);
  1409. if (idx >= 0) {
  1410. animators.splice(idx, 1);
  1411. }
  1412. });
  1413. this.animators.push(animator);
  1414. // If animate after added to the zrender
  1415. if (zr) {
  1416. zr.animation.addAnimator(animator);
  1417. }
  1418. // Wake up zrender to start the animation loop.
  1419. zr && zr.wakeUp();
  1420. }
  1421. updateDuringAnimation(key: string) {
  1422. this.markRedraw();
  1423. }
  1424. /**
  1425. * 停止动画
  1426. * @param {boolean} forwardToLast If move to last frame before stop
  1427. */
  1428. stopAnimation(scope?: string, forwardToLast?: boolean) {
  1429. const animators = this.animators;
  1430. const len = animators.length;
  1431. const leftAnimators: Animator<any>[] = [];
  1432. for (let i = 0; i < len; i++) {
  1433. const animator = animators[i];
  1434. if (!scope || scope === animator.scope) {
  1435. animator.stop(forwardToLast);
  1436. }
  1437. else {
  1438. leftAnimators.push(animator);
  1439. }
  1440. }
  1441. this.animators = leftAnimators;
  1442. return this;
  1443. }
  1444. /**
  1445. * @param animationProps A map to specify which property to animate. If not specified, will animate all.
  1446. * @example
  1447. * // Animate position
  1448. * el.animateTo({
  1449. * position: [10, 10]
  1450. * }, { done: () => { // done } })
  1451. *
  1452. * // Animate shape, style and position in 100ms, delayed 100ms, with cubicOut easing
  1453. * el.animateTo({
  1454. * shape: {
  1455. * width: 500
  1456. * },
  1457. * style: {
  1458. * fill: 'red'
  1459. * }
  1460. * position: [10, 10]
  1461. * }, {
  1462. * duration: 100,
  1463. * delay: 100,
  1464. * easing: 'cubicOut',
  1465. * done: () => { // done }
  1466. * })
  1467. */
  1468. animateTo(target: Props, cfg?: ElementAnimateConfig, animationProps?: MapToType<Props, boolean>) {
  1469. animateTo(this, target, cfg, animationProps);
  1470. }
  1471. /**
  1472. * Animate from the target state to current state.
  1473. * The params and the value are the same as `this.animateTo`.
  1474. */
  1475. // Overload definitions
  1476. animateFrom(
  1477. target: Props, cfg: ElementAnimateConfig, animationProps?: MapToType<Props, boolean>
  1478. ) {
  1479. animateTo(this, target, cfg, animationProps, true);
  1480. }
  1481. protected _transitionState(
  1482. stateName: string, target: Props, cfg?: ElementAnimateConfig, animationProps?: MapToType<Props, boolean>
  1483. ) {
  1484. const animators = animateTo(this, target, cfg, animationProps);
  1485. for (let i = 0; i < animators.length; i++) {
  1486. animators[i].__fromStateTransition = stateName;
  1487. }
  1488. }
  1489. /**
  1490. * Interface of getting the minimum bounding box.
  1491. */
  1492. getBoundingRect(): BoundingRect {
  1493. return null;
  1494. }
  1495. getPaintRect(): BoundingRect {
  1496. return null;
  1497. }
  1498. /**
  1499. * The string value of `textPosition` needs to be calculated to a real postion.
  1500. * For example, `'inside'` is calculated to `[rect.width/2, rect.height/2]`
  1501. * by default. See `contain/text.js#calculateTextPosition` for more details.
  1502. * But some coutom shapes like "pin", "flag" have center that is not exactly
  1503. * `[width/2, height/2]`. So we provide this hook to customize the calculation
  1504. * for those shapes. It will be called if the `style.textPosition` is a string.
  1505. * @param {Obejct} [out] Prepared out object. If not provided, this method should
  1506. * be responsible for creating one.
  1507. * @param {module:zrender/graphic/Style} style
  1508. * @param {Object} rect {x, y, width, height}
  1509. * @return {Obejct} out The same as the input out.
  1510. * {
  1511. * x: number. mandatory.
  1512. * y: number. mandatory.
  1513. * align: string. optional. use style.textAlign by default.
  1514. * verticalAlign: string. optional. use style.textVerticalAlign by default.
  1515. * }
  1516. */
  1517. calculateTextPosition: ElementCalculateTextPosition;
  1518. protected static initDefaultProps = (function () {
  1519. const elProto = Element.prototype;
  1520. elProto.type = 'element';
  1521. elProto.name = '';
  1522. elProto.ignore =
  1523. elProto.silent =
  1524. elProto.ignoreHostSilent =
  1525. elProto.isGroup =
  1526. elProto.draggable =
  1527. elProto.dragging =
  1528. elProto.ignoreClip = false;
  1529. elProto.__inHover = IN_HOVER_LAYER_KIND_NO;
  1530. elProto.__dirty = REDRAW_BIT;
  1531. const logs: Dictionary<boolean> = {};
  1532. function logDeprecatedError(key: string, xKey: string, yKey: string) {
  1533. if (!logs[key + xKey + yKey]) {
  1534. console.warn(`DEPRECATED: '${key}' has been deprecated. use '${xKey}', '${yKey}' instead`);
  1535. logs[key + xKey + yKey] = true;
  1536. }
  1537. }
  1538. // Legacy transform properties. position and scale
  1539. function createLegacyProperty(
  1540. key: string,
  1541. privateKey: string,
  1542. xKey: string,
  1543. yKey: string
  1544. ) {
  1545. Object.defineProperty(elProto, key, {
  1546. get() {
  1547. if (process.env.NODE_ENV !== 'production') {
  1548. logDeprecatedError(key, xKey, yKey);
  1549. }
  1550. if (!this[privateKey]) {
  1551. const pos: number[] = this[privateKey] = [];
  1552. enhanceArray(this, pos);
  1553. }
  1554. return this[privateKey];
  1555. },
  1556. set(pos: number[]) {
  1557. if (process.env.NODE_ENV !== 'production') {
  1558. logDeprecatedError(key, xKey, yKey);
  1559. }
  1560. this[xKey] = pos[0];
  1561. this[yKey] = pos[1];
  1562. this[privateKey] = pos;
  1563. enhanceArray(this, pos);
  1564. }
  1565. });
  1566. function enhanceArray(self: any, pos: number[]) {
  1567. Object.defineProperty(pos, 0, {
  1568. get() {
  1569. return self[xKey];
  1570. },
  1571. set(val: number) {
  1572. self[xKey] = val;
  1573. }
  1574. });
  1575. Object.defineProperty(pos, 1, {
  1576. get() {
  1577. return self[yKey];
  1578. },
  1579. set(val: number) {
  1580. self[yKey] = val;
  1581. }
  1582. });
  1583. }
  1584. }
  1585. if (Object.defineProperty
  1586. // Just don't support ie8
  1587. // && (!(env as any).browser.ie || (env as any).browser.version > 8)
  1588. ) {
  1589. createLegacyProperty('position', '_legacyPos', 'x', 'y');
  1590. createLegacyProperty('scale', '_legacyScale', 'scaleX', 'scaleY');
  1591. createLegacyProperty('origin', '_legacyOrigin', 'originX', 'originY');
  1592. }
  1593. })()
  1594. }
  1595. mixin(Element, Eventful);
  1596. mixin(Element, Transformable);
  1597. function animateTo<T>(
  1598. animatable: Element<T>,
  1599. target: Dictionary<any>,
  1600. cfg: ElementAnimateConfig,
  1601. animationProps: Dictionary<any>,
  1602. reverse?: boolean
  1603. ) {
  1604. cfg = cfg || {};
  1605. const animators: Animator<any>[] = [];
  1606. animateToShallow(
  1607. animatable,
  1608. '',
  1609. animatable,
  1610. target,
  1611. cfg,
  1612. animationProps,
  1613. animators,
  1614. reverse
  1615. );
  1616. let finishCount = animators.length;
  1617. let doneHappened = false;
  1618. const cfgDone = cfg.done;
  1619. const cfgAborted = cfg.aborted;
  1620. const doneCb = () => {
  1621. doneHappened = true;
  1622. finishCount--;
  1623. if (finishCount <= 0) {
  1624. doneHappened
  1625. ? (cfgDone && cfgDone())
  1626. : (cfgAborted && cfgAborted());
  1627. }
  1628. };
  1629. const abortedCb = () => {
  1630. finishCount--;
  1631. if (finishCount <= 0) {
  1632. doneHappened
  1633. ? (cfgDone && cfgDone())
  1634. : (cfgAborted && cfgAborted());
  1635. }
  1636. };
  1637. // No animators. This should be checked before animators[i].start(),
  1638. // because 'done' may be executed immediately if no need to animate.
  1639. if (!finishCount) {
  1640. cfgDone && cfgDone();
  1641. }
  1642. // Adding during callback to the first animator
  1643. if (animators.length > 0 && cfg.during) {
  1644. // TODO If there are two animators in animateTo, and the first one is stopped by other animator.
  1645. animators[0].during((target, percent) => {
  1646. cfg.during(percent);
  1647. });
  1648. }
  1649. // Start after all animators created
  1650. // Incase any animator is done immediately when all animation properties are not changed
  1651. for (let i = 0; i < animators.length; i++) {
  1652. const animator = animators[i];
  1653. if (doneCb) {
  1654. animator.done(doneCb);
  1655. }
  1656. if (abortedCb) {
  1657. animator.aborted(abortedCb);
  1658. }
  1659. if (cfg.force) {
  1660. animator.duration(cfg.duration);
  1661. }
  1662. animator.start(cfg.easing);
  1663. }
  1664. return animators;
  1665. }
  1666. function copyArrShallow(source: number[], target: number[], len: number) {
  1667. for (let i = 0; i < len; i++) {
  1668. source[i] = target[i];
  1669. }
  1670. }
  1671. function is2DArray(value: any[]): value is number[][] {
  1672. return isArrayLike(value[0]);
  1673. }
  1674. function copyValue(target: Dictionary<any>, source: Dictionary<any>, key: string) {
  1675. if (isArrayLike(source[key])) {
  1676. if (!isArrayLike(target[key])) {
  1677. target[key] = [];
  1678. }
  1679. if (isTypedArray(source[key])) {
  1680. const len = source[key].length;
  1681. if (target[key].length !== len) {
  1682. target[key] = new (source[key].constructor)(len);
  1683. copyArrShallow(target[key], source[key], len);
  1684. }
  1685. }
  1686. else {
  1687. const sourceArr = source[key] as any[];
  1688. const targetArr = target[key] as any[];
  1689. const len0 = sourceArr.length;
  1690. if (is2DArray(sourceArr)) {
  1691. // NOTE: each item should have same length
  1692. const len1 = sourceArr[0].length;
  1693. for (let i = 0; i < len0; i++) {
  1694. if (!targetArr[i]) {
  1695. targetArr[i] = Array.prototype.slice.call(sourceArr[i]);
  1696. }
  1697. else {
  1698. copyArrShallow(targetArr[i], sourceArr[i], len1);
  1699. }
  1700. }
  1701. }
  1702. else {
  1703. copyArrShallow(targetArr, sourceArr, len0);
  1704. }
  1705. targetArr.length = sourceArr.length;
  1706. }
  1707. }
  1708. else {
  1709. target[key] = source[key];
  1710. }
  1711. }
  1712. function isValueSame(val1: any, val2: any) {
  1713. return val1 === val2
  1714. // Only check 1 dimension array
  1715. || isArrayLike(val1) && isArrayLike(val2) && is1DArraySame(val1, val2);
  1716. }
  1717. function is1DArraySame(arr0: ArrayLike<number>, arr1: ArrayLike<number>) {
  1718. const len = arr0.length;
  1719. if (len !== arr1.length) {
  1720. return false;
  1721. }
  1722. for (let i = 0; i < len; i++) {
  1723. if (arr0[i] !== arr1[i]) {
  1724. return false;
  1725. }
  1726. }
  1727. return true;
  1728. }
  1729. function animateToShallow<T>(
  1730. animatable: Element<T>,
  1731. topKey: string,
  1732. animateObj: Dictionary<any>,
  1733. target: Dictionary<any>,
  1734. cfg: ElementAnimateConfig,
  1735. animationProps: Dictionary<any> | true,
  1736. animators: Animator<any>[],
  1737. reverse: boolean // If `true`, animate from the `target` to current state.
  1738. ) {
  1739. const targetKeys = keys(target);
  1740. const duration = cfg.duration;
  1741. const delay = cfg.delay;
  1742. const additive = cfg.additive;
  1743. const setToFinal = cfg.setToFinal;
  1744. const animateAll = !isObject(animationProps);
  1745. // Find last animator animating same prop.
  1746. const existsAnimators = animatable.animators;
  1747. let animationKeys: string[] = [];
  1748. for (let k = 0; k < targetKeys.length; k++) {
  1749. const innerKey = targetKeys[k] as string;
  1750. const targetVal = target[innerKey];
  1751. if (
  1752. targetVal != null && animateObj[innerKey] != null
  1753. && (animateAll || (animationProps as Dictionary<any>)[innerKey])
  1754. ) {
  1755. if (isObject(targetVal)
  1756. && !isArrayLike(targetVal)
  1757. && !isGradientObject(targetVal)
  1758. ) {
  1759. if (topKey) {
  1760. // logError('Only support 1 depth nest object animation.');
  1761. // Assign directly.
  1762. // TODO richText?
  1763. if (!reverse) {
  1764. animateObj[innerKey] = targetVal;
  1765. animatable.updateDuringAnimation(topKey);
  1766. }
  1767. continue;
  1768. }
  1769. animateToShallow(
  1770. animatable,
  1771. innerKey,
  1772. animateObj[innerKey],
  1773. targetVal,
  1774. cfg,
  1775. animationProps && (animationProps as Dictionary<any>)[innerKey],
  1776. animators,
  1777. reverse
  1778. );
  1779. }
  1780. else {
  1781. animationKeys.push(innerKey);
  1782. }
  1783. }
  1784. else if (!reverse) {
  1785. // Assign target value directly.
  1786. animateObj[innerKey] = targetVal;
  1787. animatable.updateDuringAnimation(topKey);
  1788. // Previous animation will be stopped on the changed keys.
  1789. // So direct assign is also included.
  1790. animationKeys.push(innerKey);
  1791. }
  1792. }
  1793. let keyLen = animationKeys.length;
  1794. // Stop previous animations on the same property.
  1795. if (!additive && keyLen) {
  1796. // Stop exists animation on specific tracks. Only one animator available for each property.
  1797. // TODO Should invoke previous animation callback?
  1798. for (let i = 0; i < existsAnimators.length; i++) {
  1799. const animator = existsAnimators[i];
  1800. if (animator.targetName === topKey) {
  1801. const allAborted = animator.stopTracks(animationKeys);
  1802. if (allAborted) { // This animator can't be used.
  1803. const idx = indexOf(existsAnimators, animator);
  1804. existsAnimators.splice(idx, 1);
  1805. }
  1806. }
  1807. }
  1808. }
  1809. // Ignore values not changed.
  1810. // NOTE: Must filter it after previous animation stopped
  1811. // and make sure the value to compare is using initial frame if animation is not started yet when setToFinal is used.
  1812. if (!cfg.force) {
  1813. animationKeys = filter(animationKeys, key => !isValueSame(target[key], animateObj[key]));
  1814. keyLen = animationKeys.length;
  1815. }
  1816. if (keyLen > 0
  1817. // cfg.force is mainly for keep invoking onframe and ondone callback even if animation is not necessary.
  1818. // So if there is already has animators. There is no need to create another animator if not necessary.
  1819. // Or it will always add one more with empty target.
  1820. || (cfg.force && !animators.length)
  1821. ) {
  1822. let revertedSource: Dictionary<any>;
  1823. let reversedTarget: Dictionary<any>;
  1824. let sourceClone: Dictionary<any>;
  1825. if (reverse) {
  1826. reversedTarget = {};
  1827. if (setToFinal) {
  1828. revertedSource = {};
  1829. }
  1830. for (let i = 0; i < keyLen; i++) {
  1831. const innerKey = animationKeys[i];
  1832. reversedTarget[innerKey] = animateObj[innerKey];
  1833. if (setToFinal) {
  1834. revertedSource[innerKey] = target[innerKey];
  1835. }
  1836. else {
  1837. // The usage of "animateFrom" expects that the element props has been updated dirctly to
  1838. // "final" values outside, and input the "from" values here (i.e., in variable `target` here).
  1839. // So here we assign the "from" values directly to element here (rather that in the next frame)
  1840. // to prevent the "final" values from being read in any other places (like other running
  1841. // animator during callbacks).
  1842. // But if `setToFinal: true` this feature can not be satisfied.
  1843. animateObj[innerKey] = target[innerKey];
  1844. }
  1845. }
  1846. }
  1847. else if (setToFinal) {
  1848. sourceClone = {};
  1849. for (let i = 0; i < keyLen; i++) {
  1850. const innerKey = animationKeys[i];
  1851. // NOTE: Must clone source after the stopTracks. The property may be modified in stopTracks.
  1852. sourceClone[innerKey] = cloneValue(animateObj[innerKey]);
  1853. // Use copy, not change the original reference
  1854. // Copy from target to source.
  1855. copyValue(animateObj, target, innerKey);
  1856. }
  1857. }
  1858. const animator = new Animator(animateObj, false, false, additive ? filter(
  1859. // Use key string instead object reference because ref may be changed.
  1860. existsAnimators, animator => animator.targetName === topKey
  1861. ) : null);
  1862. animator.targetName = topKey;
  1863. if (cfg.scope) {
  1864. animator.scope = cfg.scope;
  1865. }
  1866. if (setToFinal && revertedSource) {
  1867. animator.whenWithKeys(0, revertedSource, animationKeys);
  1868. }
  1869. if (sourceClone) {
  1870. animator.whenWithKeys(0, sourceClone, animationKeys);
  1871. }
  1872. animator.whenWithKeys(
  1873. duration == null ? 500 : duration,
  1874. reverse ? reversedTarget : target,
  1875. animationKeys
  1876. ).delay(delay || 0);
  1877. animatable.addAnimator(animator, topKey);
  1878. animators.push(animator);
  1879. }
  1880. }
  1881. function shouldUseHoverLayer(
  1882. el: Element,
  1883. textContent: Element,
  1884. nextState: ElementState,
  1885. forceUseHoverLayer: boolean
  1886. ): InHoverLayerKind {
  1887. return (
  1888. !((nextState && nextState.hoverLayer) || forceUseHoverLayer)
  1889. // PENDING: See HOVER_LAYER_CONSTRAINTS_TEXT for the reasons.
  1890. || isTextRelatedEl(el)
  1891. || (textContent && isTextRelatedEl(textContent))
  1892. )
  1893. ? IN_HOVER_LAYER_KIND_NO
  1894. // If using haver layer and previously it is not in a hover layer and invisible.
  1895. // PENDING: See HOVER_LAYER_CONSTRAINTS_TEXT for the reasons.
  1896. // : (!el.__inHover && (el.ignore || (el as DisplayableProps).invisible))
  1897. // ? IN_HOVER_LAYER_KIND_NO_LIMIT
  1898. // Otherwise (typically, perviously anything has been painted on the original layer),
  1899. // only styles can be modified. See more detailed reasons in `HOVER_LAYER_CONSTRAINTS`.
  1900. : IN_HOVER_LAYER_KIND_ONLY_STYLE_CHANGE;
  1901. }
  1902. function isTextRelatedEl(el: Element<ElementProps>): boolean {
  1903. return el.type === 'text' || el.type === 'tspan';
  1904. }
  1905. function canTransition(
  1906. el: Element,
  1907. noAnimation: boolean,
  1908. animationCfg: ElementAnimateConfig
  1909. ): boolean {
  1910. return !noAnimation && !el.__inHover && animationCfg && animationCfg.duration > 0;
  1911. }
  1912. export default Element;