Inisital Asset Commit

This commit is contained in:
Hickmeister
2025-01-03 23:10:59 +00:00
parent 18cb77cb3d
commit b1be0cb849
833 changed files with 159911 additions and 0 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,127 @@
import { show, customProperty, detectAnimation, ClassName } from './util'
import { buildClickStepLinearListener, buildClickStepNonLinearListener } from './listeners'
const DEFAULT_OPTIONS = {
linear: true,
animation: false,
selectors: {
steps: '.step',
trigger: '.step-trigger',
stepper: '.bs-stepper'
}
}
class Stepper {
constructor (element, _options = {}) {
this._element = element
this._currentIndex = 0
this._stepsContents = []
this.options = {
...DEFAULT_OPTIONS,
..._options
}
this.options.selectors = {
...DEFAULT_OPTIONS.selectors,
...this.options.selectors
}
if (this.options.linear) {
this._element.classList.add(ClassName.LINEAR)
}
this._steps = [].slice.call(this._element.querySelectorAll(this.options.selectors.steps))
this._steps.filter(step => step.hasAttribute('data-target'))
.forEach(step => {
this._stepsContents.push(
this._element.querySelector(step.getAttribute('data-target'))
)
})
detectAnimation(this._stepsContents, this.options)
this._setLinkListeners()
Object.defineProperty(this._element, customProperty, {
value: this,
writable: true
})
if (this._steps.length) {
show(this._element, this._currentIndex, this.options, () => {})
}
}
// Private
_setLinkListeners () {
this._steps.forEach(step => {
const trigger = step.querySelector(this.options.selectors.trigger)
if (this.options.linear) {
this._clickStepLinearListener = buildClickStepLinearListener(this.options)
trigger.addEventListener('click', this._clickStepLinearListener)
} else {
this._clickStepNonLinearListener = buildClickStepNonLinearListener(this.options)
trigger.addEventListener('click', this._clickStepNonLinearListener)
}
})
}
// Public
next () {
const nextStep = (this._currentIndex + 1) <= this._steps.length - 1 ? this._currentIndex + 1 : (this._steps.length - 1)
show(this._element, nextStep, this.options, () => {
this._currentIndex = nextStep
})
}
previous () {
const previousStep = (this._currentIndex - 1) >= 0 ? this._currentIndex - 1 : 0
show(this._element, previousStep, this.options, () => {
this._currentIndex = previousStep
})
}
to (stepNumber) {
const tempIndex = stepNumber - 1
const nextStep = tempIndex >= 0 && tempIndex < this._steps.length
? tempIndex
: 0
show(this._element, nextStep, this.options, () => {
this._currentIndex = nextStep
})
}
reset () {
show(this._element, 0, this.options, () => {
this._currentIndex = 0
})
}
destroy () {
this._steps.forEach(step => {
const trigger = step.querySelector(this.options.selectors.trigger)
if (this.options.linear) {
trigger.removeEventListener('click', this._clickStepLinearListener)
} else {
trigger.removeEventListener('click', this._clickStepNonLinearListener)
}
})
this._element[customProperty] = undefined
this._element = undefined
this._currentIndex = undefined
this._steps = undefined
this._stepsContents = undefined
this._clickStepLinearListener = undefined
this._clickStepNonLinearListener = undefined
}
}
export default Stepper

View File

@@ -0,0 +1,677 @@
/* eslint-env jasmine */
import Stepper from './index'
describe('Stepper', () => {
let fixture
beforeAll(() => {
const fixureNode = document.createElement('div')
fixureNode.setAttribute('id', 'fixture')
document.body.appendChild(fixureNode)
fixture = document.getElementById('fixture')
})
afterEach(() => {
fixture.innerHTML = ''
})
describe('constructor', () => {
it('should create a stepper', () => {
fixture.innerHTML = [
'<div id="myStepper" class="bs-stepper">',
' <div class="step" data-target="#test1">',
' <button class="step-trigger" id="trigger1">1</button>',
' </div>',
' <div class="step" data-target="#test2">',
' <button class="step-trigger" id="trigger2">2</button>',
' </div>',
' <div id="test1">1</div>',
' <div id="test2">2</div>',
'</div>'
].join('')
const stepperNode = document.getElementById('myStepper')
const stepper = new Stepper(stepperNode)
expect(stepperNode.classList.contains('linear')).toBe(true)
expect(stepper._steps.length).toEqual(2)
expect(stepperNode['bsStepper']).toEqual(stepper)
expect(document.querySelector('.step').classList.contains('active')).toBe(true)
expect(document.getElementById('trigger1').getAttribute('aria-selected')).toEqual('true')
expect(document.getElementById('trigger2').getAttribute('aria-selected')).toEqual('false')
expect(stepper.options).toEqual({
linear: true,
animation: false,
selectors: {
steps: '.step',
trigger: '.step-trigger',
stepper: '.bs-stepper'
}
})
})
it('should do nothing if there is no step', () => {
fixture.innerHTML = '<div id="myStepper" class="bs-stepper"></div>'
const stepperNode = document.getElementById('myStepper')
const stepper = new Stepper(stepperNode)
expect(stepperNode.classList.contains('linear')).toBe(true)
expect(stepper._steps.length).toEqual(0)
expect(stepperNode['bsStepper']).toEqual(stepper)
})
it('should create a non linear stepper', () => {
fixture.innerHTML = [
'<div id="myStepper" class="bs-stepper">',
' <div class="step" data-target="#test1">',
' <button class="step-trigger">1</button>',
' </div>',
' <div class="step" data-target="#test2">',
' <button class="step-trigger">2</button>',
' </div>',
' <div id="test1">1</div>',
' <div id="test2">2</div>',
'</div>'
].join('')
const stepperNode = document.getElementById('myStepper')
const stepper = new Stepper(stepperNode, {
linear: false
})
expect(stepperNode.classList.contains('linear')).toBe(false)
expect(stepper._steps.length).toEqual(2)
expect(document.querySelector('.step').classList.contains('active')).toBe(true)
expect(stepperNode['bsStepper']).toEqual(stepper)
expect(stepper._clickStepLinearListener).toBeUndefined()
expect(stepper._clickStepNonLinearListener).toBeTruthy()
expect(stepper.options).toEqual({
linear: false,
animation: false,
selectors: {
steps: '.step',
trigger: '.step-trigger',
stepper: '.bs-stepper'
}
})
})
it('should go to the next step when user click on a step for non linear stepper', () => {
fixture.innerHTML = [
'<div id="myStepper" class="bs-stepper">',
' <div class="step" data-target="#test1">',
' <button id="trigger1" class="step-trigger">1</button>',
' </div>',
' <div class="step" data-target="#test2">',
' <button id="trigger2" class="step-trigger">2</button>',
' </div>',
' <div id="test1">1</div>',
' <div id="test2">2</div>',
'</div>'
].join('')
const stepperNode = document.getElementById('myStepper')
const stepper = new Stepper(stepperNode, {
linear: false
})
const trigger2 = document.querySelector('#trigger2')
trigger2.click()
expect(document.querySelector('#test1').classList.contains('active')).toBe(false)
expect(document.querySelector('#test2').classList.contains('active')).toBe(true)
expect(document.getElementById('trigger1').getAttribute('aria-selected')).toEqual('false')
expect(document.getElementById('trigger2').getAttribute('aria-selected')).toEqual('true')
expect(stepper._currentIndex).toEqual(1)
})
it('should call preventDefault when user click on a step for linear stepper', () => {
fixture.innerHTML = [
'<div id="myStepper" class="bs-stepper">',
' <div class="step" data-target="#test1">',
' <button class="step-trigger">1</button>',
' </div>',
' <div class="step" data-target="#test2">',
' <button id="trigger2" class="step-trigger">2</button>',
' </div>',
' <div id="test1">1</div>',
' <div id="test2">2</div>',
'</div>'
].join('')
const stepperNode = document.getElementById('myStepper')
const stepper = new Stepper(stepperNode)
const trigger2 = document.querySelector('#trigger2')
trigger2.removeAttribute('disabled')
const clickEvent = document.createEvent('Event')
clickEvent.initEvent('click', true, true)
spyOn(clickEvent, 'preventDefault')
trigger2.dispatchEvent(clickEvent)
expect(clickEvent.preventDefault).toHaveBeenCalled()
expect(stepper._currentIndex).toEqual(0)
})
it('should create a stepper with fade animation', done => {
fixture.innerHTML = [
'<div id="myStepper" class="bs-stepper">',
' <div class="step" data-target="#test1">',
' <button class="step-trigger">1</button>',
' </div>',
' <div class="step" data-target="#test2">',
' <button class="step-trigger">2</button>',
' </div>',
' <div id="test1">1</div>',
' <div id="test2">2</div>',
'</div>'
].join('')
const stepperNode = document.getElementById('myStepper')
const stepper = new Stepper(stepperNode, {
animation: true
})
setTimeout(() => {
expect(stepper.options).toEqual({
linear: true,
animation: true,
selectors: {
steps: '.step',
trigger: '.step-trigger',
stepper: '.bs-stepper'
}
})
expect(document.querySelector('#test1').classList.contains('fade')).toBe(true)
expect(document.querySelector('#test2').classList.contains('fade')).toBe(true)
done()
}, 10)
})
it('should add event listeners on triggers', () => {
fixture.innerHTML = [
'<div id="myStepper" class="bs-stepper">',
' <div class="step" data-target="#test1">',
' <button id="trigger1" class="step-trigger">1</button>',
' </div>',
' <div class="step" data-target="#test2">',
' <button id="trigger2" class="step-trigger">2</button>',
' </div>',
' <div id="test1">1</div>',
' <div id="test2">2</div>',
'</div>'
].join('')
const trigger1 = document.querySelector('#trigger1')
const trigger2 = document.querySelector('#trigger2')
spyOn(trigger1, 'addEventListener')
spyOn(trigger2, 'addEventListener')
const stepperNode = document.getElementById('myStepper')
const stepper = new Stepper(stepperNode)
expect(trigger1.addEventListener).toHaveBeenCalled()
expect(trigger2.addEventListener).toHaveBeenCalled()
expect(stepperNode['bsStepper']).toEqual(stepper)
})
it('should allow css selector configuration', () => {
fixture.innerHTML = [
'<div id="myStepper" class="custom-bs-stepper">',
' <div class="custom-step" data-target="#test1">',
' <button id="trigger1" class="custom-step-trigger">1</button>',
' </div>',
' <div class="custom-step" data-target="#test2">',
' <button id="trigger2" class="custom-step-trigger">2</button>',
' </div>',
' <div id="test1">1</div>',
' <div id="test2">2</div>',
'</div>'
].join('')
const stepperNode = document.getElementById('myStepper')
const stepper = new Stepper(stepperNode, {
selectors: {
steps: '.custom-step',
trigger: '.custom-step-trigger',
stepper: '.custom-bs-stepper'
}
})
expect(stepper.options).toEqual({
linear: true,
animation: false,
selectors: {
steps: '.custom-step',
trigger: '.custom-step-trigger',
stepper: '.custom-bs-stepper'
}
})
})
})
describe('next', () => {
it('should go to the next step', done => {
fixture.innerHTML = [
'<div id="myStepper" class="bs-stepper">',
' <div class="step" data-target="#test1">',
' <button class="step-trigger">1</button>',
' </div>',
' <div class="step" data-target="#test2">',
' <button class="step-trigger">2</button>',
' </div>',
' <div id="test1">1</div>',
' <div id="test2">2</div>',
'</div>'
].join('')
const stepperNode = document.getElementById('myStepper')
const stepper = new Stepper(stepperNode)
stepperNode.addEventListener('show.bs-stepper', function (event) {
expect(event.detail.indexStep).toEqual(1)
expect(event.detail.to).toEqual(1)
expect(event.detail.from).toEqual(0)
})
stepperNode.addEventListener('shown.bs-stepper', function (event) {
expect(event.detail.indexStep).toEqual(1)
expect(event.detail.to).toEqual(1)
expect(event.detail.from).toEqual(0)
expect(document.querySelector('#test1').classList.contains('active')).toBe(false)
expect(document.querySelector('#test2').classList.contains('active')).toBe(true)
done()
})
stepper.next()
})
it('should go to the next step with css selector configuration', done => {
fixture.innerHTML = [
'<div id="myStepper" class="custom-bs-stepper">',
' <div class="custom-step" data-target="#test1">',
' <button id="trigger1" class="custom-step-trigger">1</button>',
' </div>',
' <div class="custom-step" data-target="#test2">',
' <button id="trigger2" class="custom-step-trigger">2</button>',
' </div>',
' <div id="test1">1</div>',
' <div id="test2">2</div>',
'</div>'
].join('')
const stepperNode = document.getElementById('myStepper')
const stepper = new Stepper(stepperNode, {
selectors: {
steps: '.custom-step',
trigger: '.custom-step-trigger',
stepper: '.custom-bs-stepper'
}
})
expect(stepper.options).toEqual({
linear: true,
animation: false,
selectors: {
steps: '.custom-step',
trigger: '.custom-step-trigger',
stepper: '.custom-bs-stepper'
}
})
stepperNode.addEventListener('show.bs-stepper', function (event) {
expect(event.detail.indexStep).toEqual(1)
})
stepperNode.addEventListener('shown.bs-stepper', function (event) {
expect(event.detail.indexStep).toEqual(1)
expect(document.querySelector('#test1').classList.contains('active')).toBe(false)
expect(document.querySelector('#test2').classList.contains('active')).toBe(true)
done()
})
stepper.next()
})
it('should not go to the next step if the show event is default prevented', done => {
fixture.innerHTML = [
'<div id="myStepper" class="bs-stepper">',
' <div class="step" data-target="#test1">',
' <button class="step-trigger">1</button>',
' </div>',
' <div class="step" data-target="#test2">',
' <button class="step-trigger">2</button>',
' </div>',
' <div id="test1">1</div>',
' <div id="test2">2</div>',
'</div>'
].join('')
const stepperNode = document.getElementById('myStepper')
const stepper = new Stepper(stepperNode)
const listeners = {
show (event) {
event.preventDefault()
expect(event.detail.indexStep).toEqual(1)
setTimeout(() => {
expect(listeners.shown).not.toHaveBeenCalled()
expect(stepper._currentIndex).toEqual(0)
done()
}, 10)
},
shown () {
console.warn('shown called but it should not be the case')
}
}
spyOn(listeners, 'shown')
stepperNode.addEventListener('show.bs-stepper', listeners.show)
stepperNode.addEventListener('shown.bs-stepper', listeners.shown)
stepper.next()
})
it('should stay at the end if we call next', () => {
fixture.innerHTML = [
'<div id="myStepper" class="bs-stepper">',
' <div class="step" data-target="#test1">',
' <button class="step-trigger">1</button>',
' </div>',
' <div class="step" data-target="#test2">',
' <button class="step-trigger">2</button>',
' </div>',
' <div id="test1">1</div>',
' <div id="test2">2</div>',
'</div>'
].join('')
const stepperNode = document.getElementById('myStepper')
const stepper = new Stepper(stepperNode)
stepper.next()
stepper.next()
expect(document.querySelector('#test1').classList.contains('active')).toBe(false)
expect(document.querySelector('#test2').classList.contains('active')).toBe(true)
})
it('should keep block class on previous steps for vertical stepper without fade', () => {
fixture.innerHTML = [
'<div id="myStepper" class="bs-stepper vertical">',
' <div class="step" data-target="#test1">',
' <button class="step-trigger">1</button>',
' </div>',
' <div class="step" data-target="#test2">',
' <button class="step-trigger">2</button>',
' </div>',
' <div id="test1">1</div>',
' <div id="test2">2</div>',
'</div>'
].join('')
const stepperNode = document.getElementById('myStepper')
const stepper = new Stepper(stepperNode)
stepper.next()
expect(document.querySelector('#test2').classList.contains('active')).toBe(true)
expect(document.querySelector('#test2').classList.contains('dstepper-block')).toBe(true)
})
})
describe('previous', () => {
it('should return to the previous step', done => {
fixture.innerHTML = [
'<div id="myStepper" class="bs-stepper">',
' <div class="step" data-target="#test1">',
' <button class="step-trigger">1</button>',
' </div>',
' <div class="step" data-target="#test2">',
' <button class="step-trigger">2</button>',
' </div>',
' <div id="test1">1</div>',
' <div id="test2">2</div>',
'</div>'
].join('')
const stepperNode = document.getElementById('myStepper')
const stepper = new Stepper(stepperNode)
const test1 = document.querySelector('#test1')
const test2 = document.querySelector('#test2')
stepper.next()
expect(test1.classList.contains('active')).toBe(false)
expect(test2.classList.contains('active')).toBe(true)
stepperNode.addEventListener('show.bs-stepper', function (event) {
expect(event.detail.indexStep).toEqual(0)
expect(event.detail.to).toEqual(0)
expect(event.detail.from).toEqual(1)
})
stepperNode.addEventListener('shown.bs-stepper', function (event) {
expect(event.detail.indexStep).toEqual(0)
expect(event.detail.to).toEqual(0)
expect(event.detail.from).toEqual(1)
expect(test1.classList.contains('active')).toBe(true)
expect(test2.classList.contains('active')).toBe(false)
done()
})
stepper.previous()
})
it('should stay at the first step if previous called', () => {
fixture.innerHTML = [
'<div id="myStepper" class="bs-stepper">',
' <div class="step" data-target="#test1">',
' <button class="step-trigger">1</button>',
' </div>',
' <div class="step" data-target="#test2">',
' <button class="step-trigger">2</button>',
' </div>',
' <div id="test1">1</div>',
' <div id="test2">2</div>',
'</div>'
].join('')
const test1 = document.querySelector('#test1')
const test2 = document.querySelector('#test2')
const stepperNode = document.getElementById('myStepper')
const stepper = new Stepper(stepperNode)
stepper.previous()
expect(test1.classList.contains('active')).toBe(true)
expect(test2.classList.contains('active')).toBe(false)
})
})
describe('to', () => {
it('should go to the step number', done => {
fixture.innerHTML = [
'<div id="myStepper" class="bs-stepper">',
' <div class="step" data-target="#test1">',
' <button class="step-trigger">1</button>',
' </div>',
' <div class="step" data-target="#test2">',
' <button class="step-trigger">2</button>',
' </div>',
' <div id="test1">1</div>',
' <div id="test2">2</div>',
'</div>'
].join('')
const stepperNode = document.getElementById('myStepper')
const stepper = new Stepper(stepperNode)
const test1 = document.querySelector('#test1')
const test2 = document.querySelector('#test2')
stepperNode.addEventListener('show.bs-stepper', event => {
expect(event.detail.indexStep).toEqual(1)
expect(event.detail.to).toEqual(1)
expect(event.detail.from).toEqual(0)
})
stepperNode.addEventListener('shown.bs-stepper', event => {
expect(event.detail.indexStep).toEqual(1)
expect(event.detail.to).toEqual(1)
expect(event.detail.from).toEqual(0)
expect(test1.classList.contains('active')).toBe(false)
expect(test2.classList.contains('active')).toBe(true)
done()
})
stepper.to(2)
})
it('should handle wrong inputs', () => {
fixture.innerHTML = [
'<div id="myStepper" class="bs-stepper">',
' <div class="step" data-target="#test1">',
' <button class="step-trigger">1</button>',
' </div>',
' <div class="step" data-target="#test2">',
' <button class="step-trigger">2</button>',
' </div>',
' <div class="step" data-target="#test3">',
' <button class="step-trigger">3</button>',
' </div>',
' <div id="test1">1</div>',
' <div id="test2">2</div>',
' <div id="test3">2</div>',
'</div>'
].join('')
const stepperNode = document.getElementById('myStepper')
const stepper = new Stepper(stepperNode)
const test1 = document.querySelector('#test1')
const test2 = document.querySelector('#test2')
const test3 = document.querySelector('#test3')
stepper.to(-5)
expect(test1.classList.contains('active')).toBe(true)
expect(test2.classList.contains('active')).toBe(false)
expect(test3.classList.contains('active')).toBe(false)
stepper.to(2)
expect(test1.classList.contains('active')).toBe(false)
expect(test2.classList.contains('active')).toBe(true)
expect(test3.classList.contains('active')).toBe(false)
stepper.to(stepper._steps.length + 1)
expect(test1.classList.contains('active')).toBe(true)
expect(test2.classList.contains('active')).toBe(false)
expect(test3.classList.contains('active')).toBe(false)
})
})
describe('reset', () => {
it('should return to the first step', () => {
fixture.innerHTML = [
'<div id="myStepper" class="bs-stepper">',
' <div class="step" data-target="#test1">',
' <button class="step-trigger">1</button>',
' </div>',
' <div class="step" data-target="#test2">',
' <button class="step-trigger">2</button>',
' </div>',
' <div id="test1">1</div>',
' <div id="test2">2</div>',
'</div>'
].join('')
const stepperNode = document.getElementById('myStepper')
const stepper = new Stepper(stepperNode)
const test1 = document.querySelector('#test1')
const test2 = document.querySelector('#test2')
stepper.next()
expect(test1.classList.contains('active')).toBe(false)
expect(test2.classList.contains('active')).toBe(true)
stepper.reset()
expect(test1.classList.contains('active')).toBe(true)
expect(test2.classList.contains('active')).toBe(false)
})
})
describe('destroy', () => {
it('should clear properties', () => {
fixture.innerHTML = [
'<div id="myStepper" class="bs-stepper">',
' <div class="step" data-target="#test1">',
' <button class="step-trigger">1</button>',
' </div>',
' <div class="step" data-target="#test2">',
' <button class="step-trigger">2</button>',
' </div>',
' <div id="test1">1</div>',
' <div id="test2">2</div>',
'</div>'
].join('')
const stepperNode = document.getElementById('myStepper')
const stepper = new Stepper(stepperNode)
expect(stepperNode['bsStepper']).toEqual(stepper)
expect(stepper._element).toEqual(stepperNode)
expect(stepper._currentIndex).toEqual(0)
expect(stepper._steps.length).toEqual(2)
expect(stepper._stepsContents.length).toEqual(2)
expect(stepper._clickStepLinearListener).toBeTruthy()
expect(stepper._clickStepNonLinearListener).toBeUndefined()
stepper.destroy()
expect(stepperNode.bsStepper).toBeUndefined()
expect(stepper._element).toBeUndefined()
expect(stepper._currentIndex).toBeUndefined()
expect(stepper._steps).toBeUndefined()
expect(stepper._stepsContents).toBeUndefined()
expect(stepper._clickStepLinearListener).toBeUndefined()
expect(stepper._clickStepNonLinearListener).toBeUndefined()
})
it('should remove event listeners on triggers', () => {
fixture.innerHTML = [
'<div id="myStepper" class="bs-stepper">',
' <div class="step" data-target="#test1">',
' <button id="trigger1" class="step-trigger">1</button>',
' </div>',
' <div class="step" data-target="#test2">',
' <button id="trigger2" class="step-trigger">2</button>',
' </div>',
' <div id="test1">1</div>',
' <div id="test2">2</div>',
'</div>'
].join('')
const stepperNode = document.getElementById('myStepper')
const stepper = new Stepper(stepperNode, { linear: false })
const trigger1 = document.querySelector('#trigger1')
const trigger2 = document.querySelector('#trigger2')
spyOn(trigger1, 'removeEventListener')
spyOn(trigger2, 'removeEventListener')
stepper.destroy()
expect(trigger1.removeEventListener).toHaveBeenCalled()
expect(trigger2.removeEventListener).toHaveBeenCalled()
})
})
})

View File

@@ -0,0 +1,24 @@
import { closest } from './polyfill'
import { customProperty, show } from './util'
const buildClickStepLinearListener = () => function clickStepLinearListener (event) {
event.preventDefault()
}
const buildClickStepNonLinearListener = options => function clickStepNonLinearListener (event) {
event.preventDefault()
const step = closest(event.target, options.selectors.steps)
const stepperNode = closest(step, options.selectors.stepper)
const stepper = stepperNode[customProperty]
const stepIndex = stepper._steps.indexOf(step)
show(stepperNode, stepIndex, options, () => {
stepper._currentIndex = stepIndex
})
}
export {
buildClickStepLinearListener,
buildClickStepNonLinearListener
}

View File

@@ -0,0 +1,49 @@
var stepper1
var stepper2
//var stepper3
var stepper4
var stepperForm
document.addEventListener('DOMContentLoaded', function () {
stepper1 = new Stepper(document.querySelector('#stepper1'))
stepper2 = new Stepper(document.querySelector('#stepper2'), {
linear: false
})
stepper3 = new Stepper(document.querySelector('#stepper3'))
var stepperFormEl = document.querySelector('#stepperForm')
stepperForm = new Stepper(stepperFormEl, {
animation: true
})
var btnNextList = [].slice.call(document.querySelectorAll('.btn-next-form'))
var stepperPanList = [].slice.call(stepperFormEl.querySelectorAll('.bs-stepper-pane'))
var inputMailForm = document.getElementById('inputMailForm')
var inputPasswordForm = document.getElementById('inputPasswordForm')
var form = stepperFormEl.querySelector('.bs-stepper-content form')
btnNextList.forEach(function (btn) {
btn.addEventListener('click', function () {
stepperForm.next()
})
})
stepperFormEl.addEventListener('show.bs-stepper', function (event) {
form.classList.remove('was-validated')
var nextStep = event.detail.indexStep
var currentStep = nextStep
if (currentStep > 0) {
currentStep--
}
var stepperPan = stepperPanList[currentStep]
if ((stepperPan.getAttribute('id') === 'test-form-1' && !inputMailForm.value.length)
|| (stepperPan.getAttribute('id') === 'test-form-2' && !inputPasswordForm.value.length)) {
event.preventDefault()
form.classList.add('was-validated')
}
})
})

View File

@@ -0,0 +1,74 @@
let matches = window.Element.prototype.matches
let closest = (element, selector) => element.closest(selector)
let WinEvent = (inType, params) => new window.Event(inType, params)
let createCustomEvent = (eventName, params) => {
const cEvent = new window.CustomEvent(eventName, params)
return cEvent
}
/* istanbul ignore next */
function polyfill () {
if (!window.Element.prototype.matches) {
matches = window.Element.prototype.msMatchesSelector ||
window.Element.prototype.webkitMatchesSelector
}
if (!window.Element.prototype.closest) {
closest = (element, selector) => {
if (!document.documentElement.contains(element)) {
return null
}
do {
if (matches.call(element, selector)) {
return element
}
element = element.parentElement || element.parentNode
} while (element !== null && element.nodeType === 1)
return null
}
}
if (!window.Event || typeof window.Event !== 'function') {
WinEvent = (inType, params) => {
params = params || {}
const e = document.createEvent('Event')
e.initEvent(inType, Boolean(params.bubbles), Boolean(params.cancelable))
return e
}
}
if (typeof window.CustomEvent !== 'function') {
const originPreventDefault = window.Event.prototype.preventDefault
createCustomEvent = (eventName, params) => {
const evt = document.createEvent('CustomEvent')
params = params || { bubbles: false, cancelable: false, detail: null }
evt.initCustomEvent(eventName, params.bubbles, params.cancelable, params.detail)
evt.preventDefault = function () {
if (!this.cancelable) {
return
}
originPreventDefault.call(this)
Object.defineProperty(this, 'defaultPrevented', {
get: function () { return true }
})
}
return evt
}
}
}
polyfill()
export {
closest,
WinEvent,
createCustomEvent
}

View File

@@ -0,0 +1,168 @@
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
}