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

View File

@@ -0,0 +1,231 @@
.bs-stepper {
background-color: #FFF;
box-shadow: 0 4px 24px 0 rgba(34, 41, 47, .1);
border-radius: .5rem
}
.bs-stepper .bs-stepper-header {
padding: 1.5rem;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
border-bottom: 1px solid rgba(34, 41, 47, .08);
margin: 0
}
.bs-stepper .bs-stepper-header .line {
-webkit-box-flex: 0;
-webkit-flex: 0;
-ms-flex: 0;
flex: 0;
min-width: auto;
min-height: auto;
background-color: transparent;
margin: 0;
padding: 0 1.75rem;
color: #6E6B7B;
font-size: 1.5rem
}
.bs-stepper .bs-stepper-header .step {
margin-bottom: .25rem;
margin-top: .25rem
}
.bs-stepper .bs-stepper-header .step .step-trigger {
-webkit-flex-wrap: nowrap;
-ms-flex-wrap: nowrap;
flex-wrap: nowrap;
padding: 0;
font-weight: 400
}
.bs-stepper .bs-stepper-header .step .step-trigger .bs-stepper-box {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
width: 38px;
height: 38px;
padding: .5em 0;
font-weight: 500;
color: #BABFC7;
background-color: rgba(186, 191, 199, .12);
border-radius: .35rem
}
.bs-stepper .bs-stepper-header .step .step-trigger .bs-stepper-label {
text-align: left;
margin: .5rem 0 0 1rem
}
.bs-stepper .bs-stepper-header .step .step-trigger .bs-stepper-label .bs-stepper-title {
display: inherit;
color: #6E6B7B;
font-weight: 600;
line-height: 1rem;
margin-bottom: 0
}
.bs-stepper.vertical .bs-stepper-content .content:not(.active), .bs-stepper.vertical .bs-stepper-header .line {
display: none
}
.bs-stepper .bs-stepper-header .step .step-trigger .bs-stepper-label .bs-stepper-subtitle {
font-weight: 400;
font-size: .85rem;
color: #B9B9C3
}
.bs-stepper .bs-stepper-header .step .step-trigger:hover {
background-color: transparent
}
.bs-stepper .bs-stepper-header .step.active .step-trigger .bs-stepper-box {
background-color: #7367F0;
color: #FFF;
box-shadow: 0 3px 6px 0 rgba(115, 103, 240, .4)
}
.bs-stepper .bs-stepper-header .step.active .step-trigger .bs-stepper-label .bs-stepper-title {
color: #7367F0
}
.bs-stepper .bs-stepper-header .step.crossed .step-trigger .bs-stepper-box {
background-color: rgba(115, 103, 240, .12);
color: #7367F0!important
}
.bs-stepper .bs-stepper-header .step.crossed .step-trigger .bs-stepper-label .bs-stepper-title {
color: #B9B9C3
}
.bs-stepper .bs-stepper-header .step.crossed+.line {
color: #7367F0
}
.bs-stepper .bs-stepper-content {
padding: 1.5rem
}
.bs-stepper .bs-stepper-content .content {
margin-left: 0
}
.bs-stepper .bs-stepper-content .content .content-header {
margin-bottom: 1rem
}
.bs-stepper.vertical .bs-stepper-header {
border-right: 1px solid #EBE9F1;
border-bottom: none
}
.bs-stepper.vertical .bs-stepper-header .step .step-trigger {
padding: 1rem 0
}
.bs-stepper.vertical .bs-stepper-content {
width: 100%;
padding-top: 2.5rem
}
.bs-stepper.vertical.wizard-icons .step {
text-align: center
}
.bs-stepper.wizard-modern {
background-color: transparent;
box-shadow: none
}
.bs-stepper.wizard-modern .bs-stepper-header {
border: none
}
.bs-stepper.wizard-modern .bs-stepper-content {
background-color: #FFF;
border-radius: .5rem;
box-shadow: 0 4px 24px 0 rgba(34, 41, 47, .1)
}
.horizontal-wizard, .modern-horizontal-wizard, .modern-vertical-wizard, .vertical-wizard {
margin-bottom: 2.2rem
}
.dark-layout .bs-stepper {
background-color: #283046;
box-shadow: 0 4px 24px 0 rgba(34, 41, 47, .24)
}
.dark-layout .bs-stepper .bs-stepper-header {
border-bottom: 1px solid rgba(59, 66, 83, .08)
}
.dark-layout .bs-stepper .bs-stepper-header .line {
color: #B4B7BD
}
.dark-layout .bs-stepper .bs-stepper-header .step .step-trigger .bs-stepper-box {
color: #BABFC7
}
.dark-layout .bs-stepper .bs-stepper-header .step .step-trigger .bs-stepper-label .bs-stepper-title {
color: #B4B7BD
}
.dark-layout .bs-stepper .bs-stepper-header .step .step-trigger .bs-stepper-label .bs-stepper-subtitle {
color: #676D7D
}
.dark-layout .bs-stepper .bs-stepper-header .step.active .step-trigger .bs-stepper-box {
background-color: #7367F0;
color: #FFF;
box-shadow: 0 3px 6px 0 rgba(115, 103, 240, .4)
}
.dark-layout .bs-stepper .bs-stepper-header .step.active .step-trigger .bs-stepper-label .bs-stepper-title {
color: #7367F0
}
.dark-layout .bs-stepper .bs-stepper-header .step.crossed .step-trigger .bs-stepper-label, .dark-layout .bs-stepper .bs-stepper-header .step.crossed .step-trigger .bs-stepper-title {
color: #676D7D
}
.dark-layout .bs-stepper.vertical .bs-stepper-header {
border-right-color: #3B4253
}
.dark-layout .bs-stepper.wizard-modern {
background-color: transparent;
box-shadow: none
}
.dark-layout .bs-stepper.wizard-modern .bs-stepper-header {
border: none
}
.dark-layout .bs-stepper.wizard-modern .bs-stepper-content {
background-color: #283046;
box-shadow: 0 4px 24px 0 rgba(34, 41, 47, .24)
}
html[data-textdirection=rtl] .btn-next, html[data-textdirection=rtl] .btn-prev {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex
}
html[data-textdirection=rtl] .btn-next i, html[data-textdirection=rtl] .btn-next svg, html[data-textdirection=rtl] .btn-prev i, html[data-textdirection=rtl] .btn-prev svg {
-webkit-transform: rotate(-180deg);
-ms-transform: rotate(-180deg);
transform: rotate(-180deg)
}
@media (max-width:992px) {
.bs-stepper .bs-stepper-header {
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-align: start;
-webkit-align-items: flex-start;
-ms-flex-align: start;
align-items: flex-start
}
.bs-stepper .bs-stepper-header .step .step-trigger {
padding: .5rem 0!important;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row
}
.bs-stepper .bs-stepper-header .line {
display: none
}
.bs-stepper.vertical {
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column
}
.bs-stepper.vertical .bs-stepper-header {
-webkit-box-align: start;
-webkit-align-items: flex-start;
-ms-flex-align: start;
align-items: flex-start
}
.bs-stepper.vertical .bs-stepper-content {
padding-top: 1.5rem
}
}

View File

@@ -0,0 +1,208 @@
/*!
* bsStepper v{version} (https://github.com/Johann-S/bs-stepper)
* Copyright 2018 - {year} Johann-S <johann.servoire@gmail.com>
* Licensed under MIT (https://github.com/Johann-S/bs-stepper/blob/master/LICENSE)
*/
.bs-stepper .step-trigger {
display: flex;
flex-wrap: nowrap;
align-items: center;
justify-content: start;
gap: 1rem;
padding: 16px 0px;
color: #6c757d;
text-decoration: none;
white-space: nowrap;
vertical-align: middle;
user-select: none;
background-color: rgb(246 246 246 / 0%);
border: none;
border-radius: .25rem;
transition: background-color .15s ease-out, color .15s ease-out;
}
.bs-stepper .step-trigger:not(:disabled):not(.disabled) {
cursor: pointer;
}
.bs-stepper .step-trigger:disabled,
.bs-stepper .step-trigger.disabled {
pointer-events: none;
opacity: .65;
}
.bs-stepper .step-trigger:focus {
color: #007bff;
outline: none;
}
.bs-stepper .step-trigger:hover {
text-decoration: none;
background-color: rgba(0, 0, 0, .0);
}
.bs-stepper-label {
display: inline-block;
margin: .25rem;
}
.bs-stepper-header {
display: flex;
align-items: center;
}
.bs-stepper-line,
.bs-stepper .line {
flex: 1 0 32px;
min-width: 1px;
min-height: 1px;
margin: auto;
background-color: rgba(0, 0, 0, .12);
}
.bs-stepper.vertical .bs-stepper-content .content:not(.active), .bs-stepper.vertical .bs-stepper-header .line {
display: none
}
.bs-stepper-circle {
width: 2.7rem;
height: 2.7rem;
font-size: 18px;
display: flex;
align-items: center;
justify-content: center;
color: #969595;
background-color: #f0f0f0;
border-radius: 50%;
}
.step .steper-title {
font-size: 17px;
}
.step .steper-sub-title {
font-size: 13px;
}
.active .bs-stepper-circle {
color: #ffffff;
background-color: #007bff;
}
.bs-stepper-content {
padding: 0;
}
@media (max-width:992px) {
.bs-stepper.vertical {
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column
}
.bs-stepper.vertical .bs-stepper-header {
-webkit-box-align: start;
-webkit-align-items: flex-start;
-ms-flex-align: start;
align-items: flex-start
}
}
@media (max-width: 520px) {
.bs-stepper-content {
padding: 0;
}
}
.bs-stepper.vertical {
display: flex;
}
.bs-stepper.vertical .bs-stepper-header {
display: flex;
flex-direction: column;
align-items: stretch;
margin: 0;
padding: 1rem;
background-color: #fbfbfb;
border-right: 1px solid #EBE9F1;
border-bottom: none;
height: auto;
}
.bs-stepper.vertical .bs-stepper-pane,
.bs-stepper.vertical .content {
display: block;
}
.bs-stepper.vertical .bs-stepper-pane:not(.fade),
.bs-stepper.vertical .content:not(.fade) {
display: block;
visibility: hidden;
}
.bs-stepper-pane:not(.fade),
.bs-stepper .content:not(.fade) {
display: none;
}
.bs-stepper .content.fade,
.bs-stepper-pane.fade {
visibility: hidden;
transition-duration: .3s;
transition-property: opacity;
}
.bs-stepper-pane.fade.active,
.bs-stepper .content.fade.active {
visibility: visible;
opacity: 1;
}
.bs-stepper-pane.active:not(.fade),
.bs-stepper .content.active:not(.fade) {
display: block;
visibility: visible;
}
.bs-stepper-pane.dstepper-block,
.bs-stepper .content.dstepper-block {
display: block;
}
.bs-stepper:not(.vertical) .bs-stepper-pane.dstepper-none,
.bs-stepper:not(.vertical) .content.dstepper-none {
display: none;
}
.vertical .bs-stepper-pane.fade.dstepper-none,
.vertical .content.fade.dstepper-none {
display: none;
}
/* dark theme */
html.dark-theme .bs-stepper.vertical .bs-stepper-header{
background-color: #31373c;
border-right: 1px solid rgb(255 255 255 / 12%);
}
html.dark-theme .step .steper-title {
color: #cdcdce;
}
html.dark-theme .step .steper-sub-title {
color: #8f8f8f;
}
html.dark-theme .bs-stepper-circle {
color: #c7cdcf;
background-color: #212529;
}

File diff suppressed because one or more lines are too long

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
}