text: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *   http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing,
   * software distributed under the License is distributed on an
   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied.  See the License for the
   * specific language governing permissions and limitations
   * under the License.
   */
  import config from '../config'
  const utils = {}
  
  function getParentScroller (vnode) {
    if (!vnode) return null
    const vm = vnode.$el
    ? vnode : vnode.elm
    ? vnode.componentInstance || vnode.context : null
    if (!vm) return null
    const type = vm.$el && vm.$el.getAttribute('weex-type')
    if (config.scrollableTypes.indexOf(type) > -1) {
      return vm
    }
    return getParentScroller(vm.$parent)
  }
  
  function now () {
    const now = window.performance && window.performance.now
    ? window.performance.now.bind(window.performance) : Date.now
    return now()
  }
  
  function scrollElement (dSuffix, position) {
    this[`scroll${dSuffix}`] = position
  }
  /**
   * self invoked function that, given a context, steps through scrolling
   * @method step
   * @param {Object} context
   */
  function step (context) {
    // call method again on next available frame
    context.frame = window.requestAnimationFrame(step.bind(window, context))
    const time = now()
    let elapsed = (time - context.startTime) / 468
    // avoid elapsed times higher than one
    elapsed = elapsed > 1 ? 1 : elapsed
    // apply easing to elapsed time
    const value = ease(elapsed)
    const currentPosition = context.startPosition + (context.position - context.startPosition) * value
    context.method.call(context.scrollable, context.dSuffix, currentPosition)
    // return when end points have been reached
    /**
      * NOTE: should use ~~ to parse position number into integer. Otherwise
      * this two float numbers maybe have a slicely little difference, which
      * will cause this function never to stop.
    */
    if (~~currentPosition === ~~context.position) {
      window.cancelAnimationFrame(context.frame)
      return
    }
  }
  /**
   * returns result of applying ease math function to a number
   * @method ease
   * @param {Number} k
   * @returns {Number}
   */
  function ease (k) {
    return 0.5 * (1 - Math.cos(Math.PI * k))
  }
  const dom = {
    /**
     * scrollToElement
     * @param  {Vnode | VComponent} vnode
     * @param  {Object} options {offset:Number}
     *   ps: scroll-to has 'ease' and 'duration'(ms) as options.
     */
    scrollToElement: function (vnode, options) {
      const {
        isArray
      } = utils
      if (isArray(vnode)) {
        if (process.env.NODE_ENV === 'development') {
          console.warn('[vue-render] the ref passed to animation.transitionOnce is a array.')
        }
        vnode = vnode[0]
      }
      const scroller = getParentScroller(vnode)
      const scrollDirection = scroller && scroller.scrollDirection || 'vertical'
      const isWindow = !scroller
      const ct = isWindow ? document.body : scroller.$el
      const el = vnode.$el || vnode.elm
      if (ct && el) {
        // if it's a list, then the listVnode.scrollDirection is undefined. just
        // assum it is the default value 'vertical'.
        const dSuffix = ({
          horizontal: 'Left',
          vertical: 'Top'
        })[scrollDirection]
        const ctRect = ct.getBoundingClientRect()
        let elRect = el.getBoundingClientRect()
        /**
          * if it's a waterfall, and you want to scroll to a header, then just
          * scroll to the top.
        */
        if (scroller && scroller.weexType === 'waterfall' && scroller._headers && scroller._headers.indexOf(vnode.$vnode || vnode) > -1) {
          // it's in waterfall. just scroll to the top.
          elRect = ct.firstElementChild.getBoundingClientRect()
        }
        const dir = dSuffix.toLowerCase()
        let offset = (isWindow ? 0 : ct[`scroll${dSuffix}`]) + elRect[dir] - ctRect[dir]
        if (options) {
          offset += options.offset && options.offset * weex.config.env.scale || 0
          // offset *= weex.config.env.scale /* adapt offset to different screen scales. */
        }
        else if (process.env.NODE_ENV === 'development') {
          console.warn('[Vue Render] The second parameter of "scrollToElement" is required, '
          + 'otherwise it may not works well on native.')
        }
        if (options && options.animated === false) {
          return scrollElement.call(ct, dSuffix, offset)
        }
        step({
          scrollable: ct,
          startTime: now(),
          frame: null,
          startPosition: ct[`scroll${dSuffix}`],
          position: offset,
          method: scrollElement,
          dSuffix: dSuffix
        })
      }
    },
    /**
     * getComponentRect
     * @param {String} vnode
     * @param {Function} callback
     */
    getComponentRect: function (vnode, callback) {
      const {
        isArray
      } = utils
      if (isArray(vnode)) {
        if (process.env.NODE_ENV === 'development') {
          console.warn('[vue-render] the ref passed to animation.transitionOnce is a array.')
        }
        vnode = vnode[0]
      }
      const scale = window.weex.config.env.scale
      const info = {
        result: false
      }
      const rectKeys = ['width', 'height', 'top', 'bottom', 'left', 'right']
  
      function recalc (rect) {
        const res = {}
        rectKeys.forEach(key => {
          if (rect[key]) {
            res[key] = rect[key] / scale
          }
        })
        return res
      }
      if (vnode && vnode === 'viewport') {
        info.result = true
        info.size = recalc({
          width: document.documentElement.clientWidth,
          height: document.documentElement.clientHeight,
          top: 0,
          left: 0,
          right: document.documentElement.clientWidth,
          bottom: document.documentElement.clientHeight
        })
        info.contentSize = recalc({
          width: document.documentElement.offsetWidth,
          height: document.documentElement.offsetHeight
        })
      }
      else if (vnode && vnode.$el) {
        info.result = true
        info.size = recalc(vnode.$el.getBoundingClientRect())
        if (vnode.$refs.inner) {
          info.contentSize = recalc({
            width: vnode.$refs.inner.offsetWidth,
            height: vnode.$refs.inner.offsetHeight
          })
        }
        else {
          info.contentSize = recalc({
            width: vnode.$el.offsetWidth,
            height: vnode.$el.offsetHeight
          })
        }
      }
      const message = info.result ? info : {
        result: false,
        errMsg: 'Illegal parameter'
      }
      callback && callback(message)
      return message
    },
    /**
     * for adding fontFace
     * @param {string} key fontFace
     * @param {object} styles rules
     */
    addRule: function (key, styles) {
      const {
        camelToKebab,
        appendCss
      } = utils
      key = camelToKebab(key)
      let stylesText = ''
      for (const k in styles) {
        if (styles.hasOwnProperty(k)) {
          stylesText += camelToKebab(k) + ':' + styles[k] + ';'
        }
      }
      const styleText = `@${key}{${stylesText}}`
      appendCss(styleText, 'dom-added-rules')
    }
  }
  export default {
    init (weex) {
      const extendKeys = weex.utils.extendKeys
      extendKeys(utils, weex.utils, ['camelToKebab', 'appendCss', 'isArray'])
      weex.registerModule('dom', dom)
    }
  }
  