<commit_before>import Vue from 'vue'

import QIcon from '../icon/QIcon.js'
import QSpinner from '../spinner/QSpinner.js'

import BtnMixin from '../../mixins/btn.js'

import { mergeSlot } from '../../utils/slot.js'
import { stop, prevent, stopAndPrevent, listenOpts, noop } from '../../utils/event.js'
import { getTouchTarget } from '../../utils/touch.js'
import { isKeyCode } from '../../utils/key-composition.js'

const { passiveCapture } = listenOpts

let
  touchTarget = void 0,
  keyboardTarget = void 0,
  mouseTarget = void 0

const iconAttrs = { role: 'img', 'aria-hidden': 'true' }

export default Vue.extend({
  name: 'QBtn',

  mixins: [ BtnMixin ],

  props: {
    percentage: Number,
    darkPercentage: Boolean
  },

  computed: {
    hasLabel () {
      return this.label !== void 0 && this.label !== null && this.label !== ''
    },

    computedRipple () {
      return this.ripple === false
        ? false
        : Object.assign(
          { keyCodes: this.isLink === true ? [ 13, 32 ] : [ 13 ] },
          this.ripple === true ? {} : this.ripple
        )
    },

    percentageStyle () {
      const val = Math.max(0, Math.min(100, this.percentage))
      if (val > 0) {
        return { transition: 'transform 0.6s', transform: `translateX(${val - 100}%)` }
      }
    },

    onEvents () {
      if (this.loading === true) {
        return {
          mousedown: this.__onLoadingEvt,
          touchstart: this.__onLoadingEvt,
          click: this.__onLoadingEvt,
          keydown: this.__onLoadingEvt,
          keyup: this.__onLoadingEvt
        }
      }
      else if (this.isActionable === true) {
        const on = {
          ...this.qListeners,
          click: this.click,
          keydown: this.__onKeydown,
          mousedown: this.__onMousedown
        }

        if (this.$q.platform.has.touch === true) {
          on.touchstart = this.__onTouchstart
        }

        return on
      }

      return {}
    },

    directives () {
      if (this.disable !== true && this.ripple !== false) {
        return [{
          name: 'ripple',
          value: this.computedRipple,
          modifiers: { center: this.round }
        }]
      }
    }
  },

  methods: {
    click (e) {
      if (e !== void 0) {
        if (e.defaultPrevented === true) {
          return
        }

        const el = document.activeElement
        // focus button if it came from ENTER on form
        // prevent the new submit (already done)
        if (
          this.type === 'submit' &&
          (
            (this.$q.platform.is.ie === true && (e.clientX < 0 || e.clientY < 0)) ||
            (
              el !== document.body &&
              this.$el.contains(el) === false &&
              // required for iOS and desktop Safari
              el.contains(this.$el) === false
            )
          )
        ) {
          this.$el.focus()

          const onClickCleanup = () => {
            document.removeEventListener('keydown', stopAndPrevent, true)
            document.removeEventListener('keyup', onClickCleanup, passiveCapture)
            this.$el !== void 0 && this.$el.removeEventListener('blur', onClickCleanup, passiveCapture)
          }

          document.addEventListener('keydown', stopAndPrevent, true)
          document.addEventListener('keyup', onClickCleanup, passiveCapture)
          this.$el.addEventListener('blur', onClickCleanup, passiveCapture)
        }

        if (this.hasRouterLink === true) {
          if (
            e.ctrlKey === true ||
            e.shiftKey === true ||
            e.altKey === true ||
            e.metaKey === true
          ) {
            // if it has meta keys, let vue-router link
            // handle this by its own
            return
          }

          stopAndPrevent(e)
        }
      }

      const go = () => {
        const res = this.$router[this.replace === true ? 'replace' : 'push'](this.to)

        // vue-router now throwing error if navigating
        // to the same route that the user is currently at
        // https://github.com/vuejs/vue-router/issues/2872
        if (res !== void 0 && typeof res.catch === 'function') {
          res.catch(noop)
        }
      }

      this.$emit('click', e, go)
      this.hasRouterLink === true && e.navigate !== false && go()
    },

    __onKeydown (e) {
      if (isKeyCode(e, [ 13, 32 ]) === true) {
        stopAndPrevent(e)

        if (keyboardTarget !== this.$el) {
          keyboardTarget !== void 0 && this.__cleanup()

          // focus external button if the focus helper was focused before
          this.$el.focus()

          keyboardTarget = this.$el
          this.$el.classList.add('q-btn--active')
          document.addEventListener('keyup', this.__onPressEnd, true)
          this.$el.addEventListener('blur', this.__onPressEnd, passiveCapture)
        }
      }

      this.$emit('keydown', e)
    },

    __onTouchstart (e) {
      if (touchTarget !== this.$el) {
        touchTarget !== void 0 && this.__cleanup()
        touchTarget = this.$el
        const target = this.touchTargetEl = getTouchTarget(e.target)
        target.addEventListener('touchcancel', this.__onPressEnd, passiveCapture)
        target.addEventListener('touchend', this.__onPressEnd, passiveCapture)
      }

      // avoid duplicated mousedown event
      // triggering another early ripple
      this.avoidMouseRipple = true
      clearTimeout(this.mouseTimer)
      this.mouseTimer = setTimeout(() => {
        this.avoidMouseRipple = false
      }, 200)

      this.$emit('touchstart', e)
    },

    __onMousedown (e) {
      if (mouseTarget !== this.$el) {
        mouseTarget !== void 0 && this.__cleanup()
        mouseTarget = this.$el
        this.$el.classList.add('q-btn--active')
        document.addEventListener('mouseup', this.__onPressEnd, passiveCapture)
      }

      e.qSkipRipple = this.avoidMouseRipple === true
      this.$emit('mousedown', e)
    },

    __onPressEnd (e) {
      // needed for IE (because it emits blur when focusing button from focus helper)
      if (e !== void 0 && e.type === 'blur' && document.activeElement === this.$el) {
        return
      }

      if (e !== void 0 && e.type === 'keyup') {
        if (keyboardTarget === this.$el && isKeyCode(e, [ 13, 32 ]) === true) {
          // for click trigger
          const evt = new MouseEvent('click', e)
          evt.qKeyEvent = true
          e.defaultPrevented === true && prevent(evt)
          e.cancelBubble === true && stop(evt)
          this.$el.dispatchEvent(evt)

          stopAndPrevent(e)

          // for ripple
          e.qKeyEvent = true
        }

        this.$emit('keyup', e)
      }

      this.__cleanup()
    },

    __cleanup (destroying) {
      if (
        destroying !== true &&
        (touchTarget === this.$el || mouseTarget === this.$el) &&
        this.$refs.blurTarget !== void 0 &&
        this.$refs.blurTarget !== document.activeElement
      ) {
        this.$refs.blurTarget.focus()
      }

      if (touchTarget === this.$el) {
        const target = this.touchTargetEl
        target.removeEventListener('touchcancel', this.__onPressEnd, passiveCapture)
        target.removeEventListener('touchend', this.__onPressEnd, passiveCapture)
        touchTarget = this.touchTargetEl = void 0
      }

      if (mouseTarget === this.$el) {
        document.removeEventListener('mouseup', this.__onPressEnd, passiveCapture)
        mouseTarget = void 0
      }

      if (keyboardTarget === this.$el) {
        document.removeEventListener('keyup', this.__onPressEnd, true)
        this.$el !== void 0 && this.$el.removeEventListener('blur', this.__onPressEnd, passiveCapture)
        keyboardTarget = void 0
      }

      this.$el !== void 0 && this.$el.classList.remove('q-btn--active')
    },

    __onLoadingEvt (evt) {
      stopAndPrevent(evt)
      evt.qSkipRipple = true
    }
  },

  beforeDestroy () {
    this.__cleanup(true)
  },

  render (h) {
    let inner = []

    this.icon !== void 0 && inner.push(
      h(QIcon, {
        attrs: iconAttrs,
        props: { name: this.icon, left: this.stack === false && this.hasLabel === true }
      })
    )

    this.hasLabel === true && inner.push(
      h('div', [ this.label ])
    )

    inner = mergeSlot(inner, this, 'default')

    if (this.iconRight !== void 0 && this.round === false) {
      inner.push(
        h(QIcon, {
          attrs: iconAttrs,
          props: { name: this.iconRight, right: this.stack === false && this.hasLabel === true }
        })
      )
    }

    const child = [
      h('div', {
        staticClass: 'q-focus-helper',
        ref: 'blurTarget',
        attrs: { tabindex: -1 }
      })
    ]

    if (this.loading === true && this.percentage !== void 0) {
      child.push(
        h('div', {
          staticClass: 'q-btn__progress absolute-full overflow-hidden'
        }, [
          h('div', {
            staticClass: 'q-btn__progress-indicator fit',
            class: this.darkPercentage === true ? 'q-btn__progress--dark' : '',
            style: this.percentageStyle
          })
        ])
      )
    }

    child.push(
      h('div', {
        staticClass: 'q-btn__wrapper col row q-anchor--skip'
      }, [
        h('div', {
          staticClass: 'q-btn__content text-center col items-center q-anchor--skip',
          class: this.innerClasses
        }, inner)
      ])
    )

    this.loading !== null && child.push(
      h('transition', {
        props: { name: 'q-transition--fade' }
      }, this.loading === true ? [
        h('div', {
          key: 'loading',
          staticClass: 'absolute-full flex flex-center'
        }, this.$scopedSlots.loading !== void 0 ? this.$scopedSlots.loading() : [ h(QSpinner) ])
      ] : void 0)
    )

    return h(this.isLink === true ? 'a' : 'button', {
      staticClass: 'q-btn q-btn-item non-selectable no-outline',
      class: this.classes,
      style: this.style,
      attrs: this.attrs,
      on: this.onEvents,
      directives: this.directives
    }, child)
  }
})
<commit_msg>feat(QBtn): Use span tags for content and only set tabindex on blurTarget when needed (#6993)<commit_after>import Vue from 'vue'

import QIcon from '../icon/QIcon.js'
import QSpinner from '../spinner/QSpinner.js'

import BtnMixin from '../../mixins/btn.js'

import { mergeSlot } from '../../utils/slot.js'
import { stop, prevent, stopAndPrevent, listenOpts, noop } from '../../utils/event.js'
import { getTouchTarget } from '../../utils/touch.js'
import { isKeyCode } from '../../utils/key-composition.js'

const { passiveCapture } = listenOpts

let
  touchTarget = void 0,
  keyboardTarget = void 0,
  mouseTarget = void 0

const iconAttrs = { role: 'img', 'aria-hidden': 'true' }

export default Vue.extend({
  name: 'QBtn',

  mixins: [ BtnMixin ],

  props: {
    percentage: Number,
    darkPercentage: Boolean
  },

  computed: {
    hasLabel () {
      return this.label !== void 0 && this.label !== null && this.label !== ''
    },

    computedRipple () {
      return this.ripple === false
        ? false
        : Object.assign(
          { keyCodes: this.isLink === true ? [ 13, 32 ] : [ 13 ] },
          this.ripple === true ? {} : this.ripple
        )
    },

    percentageStyle () {
      const val = Math.max(0, Math.min(100, this.percentage))
      if (val > 0) {
        return { transition: 'transform 0.6s', transform: `translateX(${val - 100}%)` }
      }
    },

    onEvents () {
      if (this.loading === true) {
        return {
          mousedown: this.__onLoadingEvt,
          touchstart: this.__onLoadingEvt,
          click: this.__onLoadingEvt,
          keydown: this.__onLoadingEvt,
          keyup: this.__onLoadingEvt
        }
      }
      else if (this.isActionable === true) {
        const on = {
          ...this.qListeners,
          click: this.click,
          keydown: this.__onKeydown,
          mousedown: this.__onMousedown
        }

        if (this.$q.platform.has.touch === true) {
          on.touchstart = this.__onTouchstart
        }

        return on
      }

      return {}
    },

    directives () {
      if (this.disable !== true && this.ripple !== false) {
        return [{
          name: 'ripple',
          value: this.computedRipple,
          modifiers: { center: this.round }
        }]
      }
    }
  },

  methods: {
    click (e) {
      if (e !== void 0) {
        if (e.defaultPrevented === true) {
          return
        }

        const el = document.activeElement
        // focus button if it came from ENTER on form
        // prevent the new submit (already done)
        if (
          this.type === 'submit' &&
          (
            (this.$q.platform.is.ie === true && (e.clientX < 0 || e.clientY < 0)) ||
            (
              el !== document.body &&
              this.$el.contains(el) === false &&
              // required for iOS and desktop Safari
              el.contains(this.$el) === false
            )
          )
        ) {
          this.$el.focus()

          const onClickCleanup = () => {
            document.removeEventListener('keydown', stopAndPrevent, true)
            document.removeEventListener('keyup', onClickCleanup, passiveCapture)
            this.$el !== void 0 && this.$el.removeEventListener('blur', onClickCleanup, passiveCapture)
          }

          document.addEventListener('keydown', stopAndPrevent, true)
          document.addEventListener('keyup', onClickCleanup, passiveCapture)
          this.$el.addEventListener('blur', onClickCleanup, passiveCapture)
        }

        if (this.hasRouterLink === true) {
          if (
            e.ctrlKey === true ||
            e.shiftKey === true ||
            e.altKey === true ||
            e.metaKey === true
          ) {
            // if it has meta keys, let vue-router link
            // handle this by its own
            return
          }

          stopAndPrevent(e)
        }
      }

      const go = () => {
        const res = this.$router[this.replace === true ? 'replace' : 'push'](this.to)

        // vue-router now throwing error if navigating
        // to the same route that the user is currently at
        // https://github.com/vuejs/vue-router/issues/2872
        if (res !== void 0 && typeof res.catch === 'function') {
          res.catch(noop)
        }
      }

      this.$emit('click', e, go)
      this.hasRouterLink === true && e.navigate !== false && go()
    },

    __onKeydown (e) {
      if (isKeyCode(e, [ 13, 32 ]) === true) {
        stopAndPrevent(e)

        if (keyboardTarget !== this.$el) {
          keyboardTarget !== void 0 && this.__cleanup()

          // focus external button if the focus helper was focused before
          this.$el.focus()

          keyboardTarget = this.$el
          this.$el.classList.add('q-btn--active')
          document.addEventListener('keyup', this.__onPressEnd, true)
          this.$el.addEventListener('blur', this.__onPressEnd, passiveCapture)
        }
      }

      this.$emit('keydown', e)
    },

    __onTouchstart (e) {
      if (touchTarget !== this.$el) {
        touchTarget !== void 0 && this.__cleanup()
        touchTarget = this.$el
        const target = this.touchTargetEl = getTouchTarget(e.target)
        target.addEventListener('touchcancel', this.__onPressEnd, passiveCapture)
        target.addEventListener('touchend', this.__onPressEnd, passiveCapture)
      }

      // avoid duplicated mousedown event
      // triggering another early ripple
      this.avoidMouseRipple = true
      clearTimeout(this.mouseTimer)
      this.mouseTimer = setTimeout(() => {
        this.avoidMouseRipple = false
      }, 200)

      this.$emit('touchstart', e)
    },

    __onMousedown (e) {
      if (mouseTarget !== this.$el) {
        mouseTarget !== void 0 && this.__cleanup()
        mouseTarget = this.$el
        this.$el.classList.add('q-btn--active')
        document.addEventListener('mouseup', this.__onPressEnd, passiveCapture)
      }

      e.qSkipRipple = this.avoidMouseRipple === true
      this.$emit('mousedown', e)
    },

    __onPressEnd (e) {
      // needed for IE (because it emits blur when focusing button from focus helper)
      if (e !== void 0 && e.type === 'blur' && document.activeElement === this.$el) {
        return
      }

      if (e !== void 0 && e.type === 'keyup') {
        if (keyboardTarget === this.$el && isKeyCode(e, [ 13, 32 ]) === true) {
          // for click trigger
          const evt = new MouseEvent('click', e)
          evt.qKeyEvent = true
          e.defaultPrevented === true && prevent(evt)
          e.cancelBubble === true && stop(evt)
          this.$el.dispatchEvent(evt)

          stopAndPrevent(e)

          // for ripple
          e.qKeyEvent = true
        }

        this.$emit('keyup', e)
      }

      this.__cleanup()
    },

    __cleanup (destroying) {
      if (
        destroying !== true &&
        (touchTarget === this.$el || mouseTarget === this.$el) &&
        this.$refs.blurTarget !== void 0 &&
        this.$refs.blurTarget !== document.activeElement
      ) {
        this.$refs.blurTarget.setAttribute('tabindex', -1)
        this.$refs.blurTarget.focus()
        this.$refs.blurTarget.removeAttribute('tabindex')
      }

      if (touchTarget === this.$el) {
        const target = this.touchTargetEl
        target.removeEventListener('touchcancel', this.__onPressEnd, passiveCapture)
        target.removeEventListener('touchend', this.__onPressEnd, passiveCapture)
        touchTarget = this.touchTargetEl = void 0
      }

      if (mouseTarget === this.$el) {
        document.removeEventListener('mouseup', this.__onPressEnd, passiveCapture)
        mouseTarget = void 0
      }

      if (keyboardTarget === this.$el) {
        document.removeEventListener('keyup', this.__onPressEnd, true)
        this.$el !== void 0 && this.$el.removeEventListener('blur', this.__onPressEnd, passiveCapture)
        keyboardTarget = void 0
      }

      this.$el !== void 0 && this.$el.classList.remove('q-btn--active')
    },

    __onLoadingEvt (evt) {
      stopAndPrevent(evt)
      evt.qSkipRipple = true
    }
  },

  beforeDestroy () {
    this.__cleanup(true)
  },

  render (h) {
    let inner = []

    this.icon !== void 0 && inner.push(
      h(QIcon, {
        attrs: iconAttrs,
        props: { name: this.icon, left: this.stack === false && this.hasLabel === true }
      })
    )

    this.hasLabel === true && inner.push(
      h('span', { staticClass: 'block' }, [ this.label ])
    )

    inner = mergeSlot(inner, this, 'default')

    if (this.iconRight !== void 0 && this.round === false) {
      inner.push(
        h(QIcon, {
          attrs: iconAttrs,
          props: { name: this.iconRight, right: this.stack === false && this.hasLabel === true }
        })
      )
    }

    const child = [
      h('span', {
        staticClass: 'q-focus-helper',
        ref: 'blurTarget'
      })
    ]

    if (this.loading === true && this.percentage !== void 0) {
      child.push(
        h('span', {
          staticClass: 'q-btn__progress absolute-full overflow-hidden'
        }, [
          h('span', {
            staticClass: 'q-btn__progress-indicator fit block',
            class: this.darkPercentage === true ? 'q-btn__progress--dark' : '',
            style: this.percentageStyle
          })
        ])
      )
    }

    child.push(
      h('span', {
        staticClass: 'q-btn__wrapper col row q-anchor--skip'
      }, [
        h('span', {
          staticClass: 'q-btn__content text-center col items-center q-anchor--skip',
          class: this.innerClasses
        }, inner)
      ])
    )

    this.loading !== null && child.push(
      h('transition', {
        props: { name: 'q-transition--fade' }
      }, this.loading === true ? [
        h('span', {
          key: 'loading',
          staticClass: 'absolute-full flex flex-center'
        }, this.$scopedSlots.loading !== void 0 ? this.$scopedSlots.loading() : [ h(QSpinner) ])
      ] : void 0)
    )

    return h(this.isLink === true ? 'a' : 'button', {
      staticClass: 'q-btn q-btn-item non-selectable no-outline',
      class: this.classes,
      style: this.style,
      attrs: this.attrs,
      on: this.onEvents,
      directives: this.directives
    }, child)
  }
})

{'fraction_non_alphanumeric': 0.14401732539252843, 'fraction_numerical': 0.0058079440862332035, 'mean_word_length': 1.9125573394495412, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 11, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4985985', 'n_tokens_mistral': 6213, 'n_tokens_neox': 5973, 'n_words': 1341}