transition-group.js 3.87 KB
import { warn, extend } from 'core/util/index'
import { transitionProps, extractTransitionData } from './transition'

const props = extend({
  tag: String,
  moveClass: String
}, transitionProps)

delete props.mode

export default {
  props,

  created () {
    const dom = this.$requireWeexModule('dom')
    this.getPosition = el => new Promise((resolve, reject) => {
      dom.getComponentRect(el.ref, res => {
        if (!res.result) {
          reject(new Error(`failed to get rect for element: ${el.tag}`))
        } else {
          resolve(res.size)
        }
      })
    })

    const animation = this.$requireWeexModule('animation')
    this.animate = (el, options) => new Promise(resolve => {
      animation.transition(el.ref, options, resolve)
    })
  },

  render (h) {
    const tag = this.tag || this.$vnode.data.tag || 'span'
    const map = Object.create(null)
    const prevChildren = this.prevChildren = this.children
    const rawChildren = this.$slots.default || []
    const children = this.children = []
    const transitionData = extractTransitionData(this)

    for (let i = 0; i < rawChildren.length; i++) {
      const c = rawChildren[i]
      if (c.tag) {
        if (c.key != null && String(c.key).indexOf('__vlist') !== 0) {
          children.push(c)
          map[c.key] = c
          ;(c.data || (c.data = {})).transition = transitionData
        } else if (process.env.NODE_ENV !== 'production') {
          const opts = c.componentOptions
          const name = opts
            ? (opts.Ctor.options.name || opts.tag)
            : c.tag
          warn(`<transition-group> children must be keyed: <${name}>`)
        }
      }
    }

    if (prevChildren) {
      const kept = []
      const removed = []
      prevChildren.forEach(c => {
        c.data.transition = transitionData

        // TODO: record before patch positions

        if (map[c.key]) {
          kept.push(c)
        } else {
          removed.push(c)
        }
      })
      this.kept = h(tag, null, kept)
      this.removed = removed
    }

    return h(tag, null, children)
  },

  beforeUpdate () {
    // force removing pass
    this.__patch__(
      this._vnode,
      this.kept,
      false, // hydrating
      true // removeOnly (!important, avoids unnecessary moves)
    )
    this._vnode = this.kept
  },

  updated () {
    const children = this.prevChildren
    const moveClass = this.moveClass || ((this.name || 'v') + '-move')
    const moveData = children.length && this.getMoveData(children[0].context, moveClass)
    if (!moveData) {
      return
    }

    // TODO: finish implementing move animations once
    // we have access to sync getComponentRect()

    // children.forEach(callPendingCbs)

    // Promise.all(children.map(c => {
    //   const oldPos = c.data.pos
    //   const newPos = c.data.newPos
    //   const dx = oldPos.left - newPos.left
    //   const dy = oldPos.top - newPos.top
    //   if (dx || dy) {
    //     c.data.moved = true
    //     return this.animate(c.elm, {
    //       styles: {
    //         transform: `translate(${dx}px,${dy}px)`
    //       }
    //     })
    //   }
    // })).then(() => {
    //   children.forEach(c => {
    //     if (c.data.moved) {
    //       this.animate(c.elm, {
    //         styles: {
    //           transform: ''
    //         },
    //         duration: moveData.duration || 0,
    //         delay: moveData.delay || 0,
    //         timingFunction: moveData.timingFunction || 'linear'
    //       })
    //     }
    //   })
    // })
  },

  methods: {
    getMoveData (context, moveClass) {
      const stylesheet = context.$options.style || {}
      return stylesheet['@TRANSITION'] && stylesheet['@TRANSITION'][moveClass]
    }
  }
}

// function callPendingCbs (c) {
//   /* istanbul ignore if */
//   if (c.elm._moveCb) {
//     c.elm._moveCb()
//   }
//   /* istanbul ignore if */
//   if (c.elm._enterCb) {
//     c.elm._enterCb()
//   }
// }