View file Maxton/maxton/assets/plugins/bs-stepper/js/util.js

File size: 4.72Kb
import { WinEvent, createCustomEvent } from './polyfill'

const MILLISECONDS_MULTIPLIER = 1000

const ClassName = {
  ACTIVE: 'active',
  LINEAR: 'linear',
  BLOCK: 'dstepper-block',
  NONE: 'dstepper-none',
  FADE: 'fade',
  VERTICAL: 'vertical'
}

const transitionEndEvent = 'transitionend'
const customProperty = 'bsStepper'

const show = (stepperNode, indexStep, options, done) => {
  const stepper = stepperNode[customProperty]

  if (stepper._steps[indexStep].classList.contains(ClassName.ACTIVE) || stepper._stepsContents[indexStep].classList.contains(ClassName.ACTIVE)) {
    return
  }

  const showEvent = createCustomEvent('show.bs-stepper', {
    cancelable: true,
    detail: {
      from: stepper._currentIndex,
      to: indexStep,
      indexStep
    }
  })
  stepperNode.dispatchEvent(showEvent)

  const activeStep = stepper._steps.filter(step => step.classList.contains(ClassName.ACTIVE))
  const activeContent = stepper._stepsContents.filter(content => content.classList.contains(ClassName.ACTIVE))

  if (showEvent.defaultPrevented) {
    return
  }

  if (activeStep.length) {
    activeStep[0].classList.remove(ClassName.ACTIVE)
  }
  if (activeContent.length) {
    activeContent[0].classList.remove(ClassName.ACTIVE)

    if (!stepperNode.classList.contains(ClassName.VERTICAL) && !stepper.options.animation) {
      activeContent[0].classList.remove(ClassName.BLOCK)
    }
  }

  showStep(stepperNode, stepper._steps[indexStep], stepper._steps, options)
  showContent(stepperNode, stepper._stepsContents[indexStep], stepper._stepsContents, activeContent, done)
}

const showStep = (stepperNode, step, stepList, options) => {
  stepList.forEach(step => {
    const trigger = step.querySelector(options.selectors.trigger)

    trigger.setAttribute('aria-selected', 'false')
    // if stepper is in linear mode, set disabled attribute on the trigger
    if (stepperNode.classList.contains(ClassName.LINEAR)) {
      trigger.setAttribute('disabled', 'disabled')
    }
  })

  step.classList.add(ClassName.ACTIVE)
  const currentTrigger = step.querySelector(options.selectors.trigger)

  currentTrigger.setAttribute('aria-selected', 'true')
  // if stepper is in linear mode, remove disabled attribute on current
  if (stepperNode.classList.contains(ClassName.LINEAR)) {
    currentTrigger.removeAttribute('disabled')
  }
}

const showContent = (stepperNode, content, contentList, activeContent, done) => {
  const stepper = stepperNode[customProperty]
  const toIndex = contentList.indexOf(content)
  const shownEvent = createCustomEvent('shown.bs-stepper', {
    cancelable: true,
    detail: {
      from: stepper._currentIndex,
      to: toIndex,
      indexStep: toIndex
    }
  })

  function complete () {
    content.classList.add(ClassName.BLOCK)
    content.removeEventListener(transitionEndEvent, complete)
    stepperNode.dispatchEvent(shownEvent)
    done()
  }

  if (content.classList.contains(ClassName.FADE)) {
    content.classList.remove(ClassName.NONE)
    const duration = getTransitionDurationFromElement(content)

    content.addEventListener(transitionEndEvent, complete)
    if (activeContent.length) {
      activeContent[0].classList.add(ClassName.NONE)
    }

    content.classList.add(ClassName.ACTIVE)
    emulateTransitionEnd(content, duration)
  } else {
    content.classList.add(ClassName.ACTIVE)
    content.classList.add(ClassName.BLOCK)
    stepperNode.dispatchEvent(shownEvent)
    done()
  }
}

const getTransitionDurationFromElement = element => {
  if (!element) {
    return 0
  }

  // Get transition-duration of the element
  let transitionDuration = window.getComputedStyle(element).transitionDuration
  const floatTransitionDuration = parseFloat(transitionDuration)

  // Return 0 if element or transition duration is not found
  if (!floatTransitionDuration) {
    return 0
  }

  // If multiple durations are defined, take the first
  transitionDuration = transitionDuration.split(',')[0]

  return parseFloat(transitionDuration) * MILLISECONDS_MULTIPLIER
}

const emulateTransitionEnd = (element, duration) => {
  let called = false
  const durationPadding = 5
  const emulatedDuration = duration + durationPadding
  function listener () {
    called = true
    element.removeEventListener(transitionEndEvent, listener)
  }

  element.addEventListener(transitionEndEvent, listener)
  window.setTimeout(() => {
    if (!called) {
      element.dispatchEvent(WinEvent(transitionEndEvent))
    }

    element.removeEventListener(transitionEndEvent, listener)
  }, emulatedDuration)
}

const detectAnimation = (contentList, options) => {
  if (options.animation) {
    contentList.forEach(content => {
      content.classList.add(ClassName.FADE)
      content.classList.add(ClassName.NONE)
    })
  }
}

export {
  show,
  ClassName,
  customProperty,
  detectAnimation
}