diff --git a/dev-example/main.js b/dev-example/main.js
index 8b3b96f..362f49b 100644
--- a/dev-example/main.js
+++ b/dev-example/main.js
@@ -7,6 +7,7 @@ import FormWizard from '../src/index'
import WizardRoute from './WizardRoute.vue'
import FormGeneratorRoute from './FormGeneratorRoute.vue'
import TestRoute from './TestRoute.vue'
+import '../dist/vue-form-wizard.min.css'
const First = {template: '
First page
'}
const Second = {template: 'Second page
'}
diff --git a/src/components/FWizard.js b/src/components/FWizard.js
new file mode 100644
index 0000000..07029a7
--- /dev/null
+++ b/src/components/FWizard.js
@@ -0,0 +1,453 @@
+import WizardButton from './WizardButton.vue'
+import WizardStep from './WizardStep.vue'
+import {isPromise, findElementAndFocus, getFocusedTabIndex} from './helpers'
+
+export default {
+ name: 'form-wizard',
+ components: {
+ WizardButton,
+ WizardStep
+ },
+ provide () {
+ return {
+ addTab: this.addTab,
+ removeTab: this.removeTab,
+ shape: this.shape,
+ color: this.color,
+ errorColor: this.errorColor,
+ reRegisterTabs: this.reRegisterTabs
+ }
+ },
+ props: {
+ title: {
+ type: String,
+ default: 'Awesome Wizard'
+ },
+ subtitle: {
+ type: String,
+ default: 'Split a complicated flow in multiple steps'
+ },
+ nextButtonText: {
+ type: String,
+ default: 'Next'
+ },
+ backButtonText: {
+ type: String,
+ default: 'Back'
+ },
+ finishButtonText: {
+ type: String,
+ default: 'Finish'
+ },
+ hideButtons: {
+ type: Boolean,
+ default: false
+ },
+ validateOnBack: Boolean,
+ /***
+ * Applies to text, border and circle
+ */
+ color: {
+ type: String,
+ default: '#e74c3c'
+ },
+ errorColor: {
+ type: String,
+ default: '#8b0000'
+ },
+ shape: {
+ type: String,
+ default: 'circle'
+ },
+ layout: {
+ type: String,
+ default: 'horizontal'
+ },
+ stepsClasses: {
+ type: [String, Array],
+ default: ''
+ },
+ stepSize: {
+ type: String,
+ default: 'md',
+ validator: (value) => {
+ let acceptedValues = ['xs', 'sm', 'md', 'lg']
+ return acceptedValues.indexOf(value) !== -1
+ }
+ },
+ /**
+ * Name of the transition when transition between steps
+ * */
+ transition: {
+ type: String,
+ default: ''
+ },
+ /***
+ *
+ * Index of the initial tab to display
+ */
+ startIndex: {
+ type: Number,
+ default: 0,
+ validator: (value) => {
+ return value >= 0
+ }
+ }
+ },
+ data () {
+ return {
+ activeTabIndex: 0,
+ currentPercentage: 0,
+ maxStep: 0,
+ loading: false,
+ tabs: []
+ }
+ },
+ computed: {
+ slotProps () {
+ return {
+ nextTab: this.nextTab,
+ prevTab: this.prevTab,
+ activeTabIndex: this.activeTabIndex,
+ isLastStep: this.isLastStep,
+ fillButtonStyle: this.fillButtonStyle
+ }
+ },
+ tabCount () {
+ return this.tabs.length
+ },
+ isLastStep () {
+ return this.activeTabIndex === this.tabCount - 1
+ },
+ isVertical () {
+ return this.layout === 'vertical'
+ },
+ displayPrevButton () {
+ return this.activeTabIndex !== 0
+ },
+ stepPercentage () {
+ return 1 / (this.tabCount * 2) * 100
+ },
+ progressBarStyle () {
+ return {
+ backgroundColor: this.color,
+ width: `${this.progress}%`,
+ color: this.color
+ }
+ },
+ fillButtonStyle () {
+ return {
+ backgroundColor: this.color,
+ borderColor: this.color,
+ color: 'white'
+ }
+ },
+ progress () {
+ let percentage = 0
+ if (this.activeTabIndex > 0) {
+ let stepsToAdd = 1
+ let stepMultiplier = 2
+ percentage = this.stepPercentage * ((this.activeTabIndex * stepMultiplier) + stepsToAdd)
+ } else {
+ percentage = this.stepPercentage
+ }
+ return percentage
+ }
+ },
+ methods: {
+ emitTabChange (prevIndex, nextIndex) {
+ // this.$emit('on-change', prevIndex, nextIndex)
+ // this.$emit('update:startIndex', nextIndex)
+ },
+ addTab (item) {
+ const index = this.$slots.default.indexOf(item.$vnode)
+ item.tabId = `t-${item.title.replace(/ /g, '')}${index}`
+ this.tabs.splice(index, 0, item)
+ // if a step is added before the current one, go to it
+ if (index < this.activeTabIndex + 1) {
+ this.maxStep = index
+ this.changeTab(this.activeTabIndex + 1, index)
+ }
+ },
+ removeTab (item) {
+ const tabs = this.tabs
+ const index = tabs.indexOf(item)
+ if (index > -1) {
+ // Go one step back if the current step is removed
+ if (index === this.activeTabIndex) {
+ this.maxStep = this.activeTabIndex - 1
+ this.changeTab(this.activeTabIndex, this.activeTabIndex - 1)
+ }
+ if (index < this.activeTabIndex) {
+ this.maxStep = this.activeTabIndex - 1
+ this.activeTabIndex = this.activeTabIndex - 1
+ this.emitTabChange(this.activeTabIndex + 1, this.activeTabIndex)
+ }
+ tabs.splice(index, 1)
+ }
+ },
+ navigateToTab (index) {
+ let validate = index > this.activeTabIndex
+ if (index <= this.maxStep) {
+ let cb = () => {
+ if (validate && index - this.activeTabIndex > 1) {
+ // validate all steps recursively until destination index
+ this.changeTab(this.activeTabIndex, this.activeTabIndex + 1)
+ this.beforeTabChange(this.activeTabIndex, cb)
+ } else {
+ this.changeTab(this.activeTabIndex, index)
+ }
+ }
+ if (validate) {
+ this.beforeTabChange(this.activeTabIndex, cb)
+ } else {
+ this.setValidationError(null)
+ cb()
+ }
+ }
+ return index <= this.maxStep
+ },
+ nextTab () {
+ let cb = () => {
+ if (this.activeTabIndex < this.tabCount - 1) {
+ this.changeTab(this.activeTabIndex, this.activeTabIndex + 1)
+ } else {
+ this.$emit('on-complete')
+ }
+ }
+ this.beforeTabChange(this.activeTabIndex, cb)
+ },
+ prevTab () {
+ let cb = () => {
+ if (this.activeTabIndex > 0) {
+ this.setValidationError(null)
+ this.changeTab(this.activeTabIndex, this.activeTabIndex - 1)
+ }
+ }
+ if (this.validateOnBack) {
+ this.beforeTabChange(this.activeTabIndex, cb)
+ } else {
+ cb()
+ }
+ },
+ focusNextTab () {
+ let tabIndex = getFocusedTabIndex(this.tabs)
+ if (tabIndex !== -1 && tabIndex < this.tabs.length - 1) {
+ let tabToFocus = this.tabs[tabIndex + 1]
+ if (tabToFocus.checked) {
+ findElementAndFocus(tabToFocus.tabId)
+ }
+ }
+ },
+ focusPrevTab () {
+ let tabIndex = getFocusedTabIndex(this.tabs)
+ if (tabIndex !== -1 && tabIndex > 0) {
+ let toFocusId = this.tabs[tabIndex - 1].tabId
+ findElementAndFocus(toFocusId)
+ }
+ },
+ setLoading (value) {
+ this.loading = value
+ this.$emit('on-loading', value)
+ },
+ setValidationError (error) {
+ this.tabs[this.activeTabIndex].validationError = error
+ this.$emit('on-error', error)
+ },
+ validateBeforeChange (promiseFn, callback) {
+ this.setValidationError(null)
+ // we have a promise
+ if (isPromise(promiseFn)) {
+ this.setLoading(true)
+ promiseFn.then((res) => {
+ this.setLoading(false)
+ let validationResult = res === true
+ this.executeBeforeChange(validationResult, callback)
+ }).catch((error) => {
+ this.setLoading(false)
+ this.setValidationError(error)
+ })
+ // we have a simple function
+ } else {
+ let validationResult = promiseFn === true
+ this.executeBeforeChange(validationResult, callback)
+ }
+ },
+ executeBeforeChange (validationResult, callback) {
+ this.$emit('on-validate', validationResult, this.activeTabIndex)
+ if (validationResult) {
+ callback()
+ } else {
+ this.tabs[this.activeTabIndex].validationError = 'error'
+ }
+ },
+ beforeTabChange (index, callback) {
+ if (this.loading) {
+ return
+ }
+ let oldTab = this.tabs[index]
+ if (oldTab && oldTab.beforeChange !== undefined) {
+ let tabChangeRes = oldTab.beforeChange()
+ this.validateBeforeChange(tabChangeRes, callback)
+ } else {
+ callback()
+ }
+ },
+ changeTab (oldIndex, newIndex, emitChangeEvent = true) {
+ let oldTab = this.tabs[oldIndex]
+ let newTab = this.tabs[newIndex]
+ if (oldTab) {
+ oldTab.active = false
+ }
+ if (newTab) {
+ newTab.active = true
+ }
+ if (emitChangeEvent && this.activeTabIndex !== newIndex) {
+ this.emitTabChange(oldIndex, newIndex)
+ }
+ this.activeTabIndex = newIndex
+ this.activateTabAndCheckStep(this.activeTabIndex)
+ return true
+ },
+ tryChangeRoute (tab) {
+ if (this.$router && tab.route) {
+ this.$router.push(tab.route)
+ }
+ },
+ checkRouteChange (route) {
+ let matchingTabIndex = -1
+ let matchingTab = this.tabs.find((tab, index) => {
+ let match = tab.route === route
+ if (match) {
+ matchingTabIndex = index
+ }
+ return match
+ })
+
+ if (matchingTab && !matchingTab.active) {
+ const shouldValidate = matchingTabIndex > this.activeTabIndex
+ this.navigateToTab(matchingTabIndex, shouldValidate)
+ }
+ },
+ deactivateTabs () {
+ this.tabs.forEach(tab => {
+ tab.active = false
+ })
+ },
+ activateTab (index) {
+ this.deactivateTabs()
+ let tab = this.tabs[index]
+ if (tab) {
+ tab.active = true
+ tab.checked = true
+ this.tryChangeRoute(tab)
+ }
+ },
+ activateTabAndCheckStep (index) {
+ this.activateTab(index)
+ if (index > this.maxStep) {
+ this.maxStep = index
+ }
+ this.activeTabIndex = index
+ },
+ initializeTabs () {
+ if (this.tabs.length > 0 && this.startIndex === 0) {
+ this.activateTab(this.activeTabIndex)
+ }
+ if (this.startIndex < this.tabs.length) {
+ this.activateTabAndCheckStep(this.startIndex)
+ } else {
+ window.console.warn(`Prop startIndex set to ${this.startIndex} is greater than the number of tabs - ${this.tabs.length}. Make sure that the starting index is less than the number of tabs registered`)
+ }
+ }
+ },
+ mounted () {
+ this.initializeTabs()
+ },
+ watch: {
+ '$route.path' (newRoute) {
+ this.checkRouteChange(newRoute)
+ }
+ },
+ render () {
+ const tabs = this.$slots.default.filter(
+ component => component.componentOptions
+ )
+ if (tabs.length !== this.tabs.length) {
+ this.tabs = tabs
+ }
+ const steps = this.tabs.map((tab, index) => {
+ return this.navigateToTab(index)}
+ transition={this.transition}
+ index={this.index}>
+
+ })
+ return ()
+ }
+}
diff --git a/src/components/TabContent.vue b/src/components/TabContent.vue
index 8a7bced..aa9ab68 100644
--- a/src/components/TabContent.vue
+++ b/src/components/TabContent.vue
@@ -1,17 +1,17 @@
-
-
-
-
+
+
+
+
+
diff --git a/src/index.js b/src/index.js
index bb1b74d..d0a974f 100644
--- a/src/index.js
+++ b/src/index.js
@@ -2,9 +2,10 @@ import FormWizard from './components/FormWizard.vue'
import TabContent from './components/TabContent.vue'
import WizardButton from './components/WizardButton.vue'
import WizardStep from './components/WizardStep.vue'
+import FWizard from './components/FWizard'
const VueFormWizard = {
install (Vue) {
- Vue.component('form-wizard', FormWizard)
+ Vue.component('form-wizard', FWizard)
Vue.component('tab-content', TabContent)
Vue.component('wizard-button', WizardButton)
Vue.component('wizard-step', WizardStep)