node_modules updated, buefy add to lib folder
This commit is contained in:
+141
@@ -0,0 +1,141 @@
|
||||
import config from '../utils/config'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
size: String,
|
||||
expanded: Boolean,
|
||||
loading: Boolean,
|
||||
rounded: Boolean,
|
||||
icon: String,
|
||||
iconPack: String,
|
||||
// Native options to use in HTML5 validation
|
||||
autocomplete: String,
|
||||
maxlength: [Number, String],
|
||||
useHtml5Validation: {
|
||||
type: Boolean,
|
||||
default: () => config.defaultUseHtml5Validation
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isValid: true,
|
||||
isFocused: false,
|
||||
newIconPack: this.iconPack || config.defaultIconPack
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* Find parent Field, max 3 levels deep.
|
||||
*/
|
||||
parentField() {
|
||||
let parent = this.$parent
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if (parent && !parent.$data._isField) {
|
||||
parent = parent.$parent
|
||||
}
|
||||
}
|
||||
return parent
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the type prop from parent if it's a Field.
|
||||
*/
|
||||
statusType() {
|
||||
if (!this.parentField) return
|
||||
if (!this.parentField.newType) return
|
||||
if (typeof this.parentField.newType === 'string') {
|
||||
return this.parentField.newType
|
||||
} else {
|
||||
for (let key in this.parentField.newType) {
|
||||
if (this.parentField.newType[key]) {
|
||||
return key
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the message prop from parent if it's a Field.
|
||||
*/
|
||||
statusMessage() {
|
||||
if (!this.parentField) return
|
||||
|
||||
return this.parentField.newMessage
|
||||
},
|
||||
|
||||
/**
|
||||
* Fix icon size for inputs, large was too big
|
||||
*/
|
||||
iconSize() {
|
||||
switch (this.size) {
|
||||
case 'is-small': return this.size
|
||||
case 'is-medium': return
|
||||
case 'is-large': return this.newIconPack === 'mdi'
|
||||
? 'is-medium'
|
||||
: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Focus method that work dynamically depending on the component.
|
||||
*/
|
||||
focus() {
|
||||
if (this.$data._elementRef === undefined) return
|
||||
|
||||
this.$nextTick(() => {
|
||||
const el = this.$el.querySelector(this.$data._elementRef)
|
||||
if (el) el.focus()
|
||||
})
|
||||
},
|
||||
|
||||
onBlur($event) {
|
||||
this.isFocused = false
|
||||
this.$emit('blur', $event)
|
||||
this.checkHtml5Validity()
|
||||
},
|
||||
|
||||
onFocus($event) {
|
||||
this.isFocused = true
|
||||
this.$emit('focus', $event)
|
||||
},
|
||||
|
||||
/**
|
||||
* Check HTML5 validation, set isValid property.
|
||||
* If validation fail, send 'is-danger' type,
|
||||
* and error message to parent if it's a Field.
|
||||
*/
|
||||
checkHtml5Validity() {
|
||||
if (!this.useHtml5Validation) return
|
||||
|
||||
if (this.$refs[this.$data._elementRef] === undefined) return
|
||||
|
||||
const el = this.$el.querySelector(this.$data._elementRef)
|
||||
|
||||
let type = null
|
||||
let message = null
|
||||
let isValid = true
|
||||
if (!el.checkValidity()) {
|
||||
type = 'is-danger'
|
||||
message = el.validationMessage
|
||||
isValid = false
|
||||
}
|
||||
this.isValid = isValid
|
||||
|
||||
this.$nextTick(() => {
|
||||
if (this.parentField) {
|
||||
// Set type only if not defined
|
||||
if (!this.parentField.type) {
|
||||
this.parentField.newType = type
|
||||
}
|
||||
// Set message only if not defined
|
||||
if (!this.parentField.message) {
|
||||
this.parentField.newMessage = message
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return this.isValid
|
||||
}
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
import Icon from '../components/icon/Icon'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
[Icon.name]: Icon
|
||||
},
|
||||
props: {
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
title: String,
|
||||
closable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
message: String,
|
||||
type: String,
|
||||
hasIcon: Boolean,
|
||||
size: String,
|
||||
iconPack: String,
|
||||
iconSize: String,
|
||||
autoClose: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 2000
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isActive: this.active
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
active(value) {
|
||||
this.isActive = value
|
||||
},
|
||||
isActive(value) {
|
||||
if (value) {
|
||||
this.setAutoClose()
|
||||
} else {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* Icon name (MDI) based on type.
|
||||
*/
|
||||
icon() {
|
||||
switch (this.type) {
|
||||
case 'is-info':
|
||||
return 'information'
|
||||
case 'is-success':
|
||||
return 'check-circle'
|
||||
case 'is-warning':
|
||||
return 'alert'
|
||||
case 'is-danger':
|
||||
return 'alert-circle'
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Close the Message and emit events.
|
||||
*/
|
||||
close() {
|
||||
this.isActive = false
|
||||
this.$emit('close')
|
||||
this.$emit('update:active', false)
|
||||
},
|
||||
/**
|
||||
* Set timer to auto close message
|
||||
*/
|
||||
setAutoClose() {
|
||||
if (this.autoClose) {
|
||||
this.timer = setTimeout(() => {
|
||||
if (this.isActive) {
|
||||
this.close()
|
||||
}
|
||||
}, this.duration)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.setAutoClose()
|
||||
}
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
import config from './config'
|
||||
import { removeElement } from './helpers'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'is-dark'
|
||||
},
|
||||
message: String,
|
||||
duration: Number,
|
||||
queue: {
|
||||
type: Boolean,
|
||||
default: undefined
|
||||
},
|
||||
position: {
|
||||
type: String,
|
||||
default: 'is-top',
|
||||
validator(value) {
|
||||
return [
|
||||
'is-top-right',
|
||||
'is-top',
|
||||
'is-top-left',
|
||||
'is-bottom-right',
|
||||
'is-bottom',
|
||||
'is-bottom-left'
|
||||
].indexOf(value) > -1
|
||||
}
|
||||
},
|
||||
container: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isActive: false,
|
||||
parentTop: null,
|
||||
parentBottom: null,
|
||||
newContainer: this.container || config.defaultContainerElement
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
correctParent() {
|
||||
switch (this.position) {
|
||||
case 'is-top-right':
|
||||
case 'is-top':
|
||||
case 'is-top-left':
|
||||
return this.parentTop
|
||||
|
||||
case 'is-bottom-right':
|
||||
case 'is-bottom':
|
||||
case 'is-bottom-left':
|
||||
return this.parentBottom
|
||||
}
|
||||
},
|
||||
transition() {
|
||||
switch (this.position) {
|
||||
case 'is-top-right':
|
||||
case 'is-top':
|
||||
case 'is-top-left':
|
||||
return {
|
||||
enter: 'fadeInDown',
|
||||
leave: 'fadeOut'
|
||||
}
|
||||
case 'is-bottom-right':
|
||||
case 'is-bottom':
|
||||
case 'is-bottom-left':
|
||||
return {
|
||||
enter: 'fadeInUp',
|
||||
leave: 'fadeOut'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
shouldQueue() {
|
||||
const queue = this.queue !== undefined
|
||||
? this.queue
|
||||
: config.defaultNoticeQueue
|
||||
|
||||
if (!queue) return false
|
||||
|
||||
return (
|
||||
this.parentTop.childElementCount > 0 ||
|
||||
this.parentBottom.childElementCount > 0
|
||||
)
|
||||
},
|
||||
|
||||
close() {
|
||||
clearTimeout(this.timer)
|
||||
this.isActive = false
|
||||
|
||||
// Timeout for the animation complete before destroying
|
||||
setTimeout(() => {
|
||||
this.$destroy()
|
||||
removeElement(this.$el)
|
||||
}, 150)
|
||||
},
|
||||
|
||||
showNotice() {
|
||||
if (this.shouldQueue()) {
|
||||
// Call recursively if should queue
|
||||
setTimeout(() => this.showNotice(), 250)
|
||||
return
|
||||
}
|
||||
this.correctParent.insertAdjacentElement('afterbegin', this.$el)
|
||||
this.isActive = true
|
||||
|
||||
if (!this.indefinite) {
|
||||
this.timer = setTimeout(() => this.close(), this.newDuration)
|
||||
}
|
||||
},
|
||||
|
||||
setupContainer() {
|
||||
this.parentTop = document.querySelector('.notices.is-top')
|
||||
this.parentBottom = document.querySelector('.notices.is-bottom')
|
||||
|
||||
if (this.parentTop && this.parentBottom) return
|
||||
|
||||
if (!this.parentTop) {
|
||||
this.parentTop = document.createElement('div')
|
||||
this.parentTop.className = 'notices is-top'
|
||||
}
|
||||
|
||||
if (!this.parentBottom) {
|
||||
this.parentBottom = document.createElement('div')
|
||||
this.parentBottom.className = 'notices is-bottom'
|
||||
}
|
||||
|
||||
const container = document.querySelector(this.newContainer) || document.body
|
||||
|
||||
container.appendChild(this.parentTop)
|
||||
container.appendChild(this.parentBottom)
|
||||
|
||||
if (this.newContainer) {
|
||||
this.parentTop.classList.add('has-custom-container')
|
||||
this.parentBottom.classList.add('has-custom-container')
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
this.setupContainer()
|
||||
},
|
||||
mounted() {
|
||||
this.showNotice()
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
export default {
|
||||
name: 'BSlotComponent',
|
||||
props: {
|
||||
component: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
},
|
||||
tag: {
|
||||
type: String,
|
||||
default: 'div'
|
||||
},
|
||||
event: {
|
||||
type: String,
|
||||
default: 'hook:updated'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
refresh() {
|
||||
this.$forceUpdate()
|
||||
},
|
||||
isVueComponent() {
|
||||
return this.component && this.component._isVue
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.isVueComponent()) {
|
||||
this.component.$on(this.event, this.refresh)
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.isVueComponent()) {
|
||||
this.component.$off(this.event, this.refresh)
|
||||
}
|
||||
},
|
||||
render(h) {
|
||||
if (this.isVueComponent()) {
|
||||
const slots = this.component.$slots[this.name]
|
||||
return h(this.tag, {}, slots)
|
||||
}
|
||||
}
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import BSlotComponent from '@utils/SlotComponent'
|
||||
|
||||
describe('BSlotComponent', () => {
|
||||
const MockComponent = {
|
||||
render: (h) => h('div', {}, 'Hello!')
|
||||
}
|
||||
const defaultEvent = 'hook:updated'
|
||||
|
||||
it('is called', () => {
|
||||
const wrapper = shallowMount(BSlotComponent, {
|
||||
propsData: {
|
||||
component: {}
|
||||
}
|
||||
})
|
||||
|
||||
expect(wrapper.name()).toBe('BSlotComponent')
|
||||
expect(wrapper.isVueInstance()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('default render', () => {
|
||||
const slot = '<span>Content</span>'
|
||||
const Component = shallowMount(MockComponent, {
|
||||
slots: {
|
||||
default: slot
|
||||
}
|
||||
})
|
||||
const wrapper = shallowMount(BSlotComponent, {
|
||||
propsData: {
|
||||
component: Component.vm
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).toBe(`<div>${slot}</div>`)
|
||||
})
|
||||
|
||||
it('render', () => {
|
||||
const slot = '<span>Content</span>'
|
||||
const slotName = 'header'
|
||||
const Component = shallowMount(MockComponent, {
|
||||
slots: {
|
||||
[slotName]: slot
|
||||
}
|
||||
})
|
||||
const tag = 'span'
|
||||
const wrapper = shallowMount(BSlotComponent, {
|
||||
propsData: {
|
||||
component: Component.vm,
|
||||
tag: tag,
|
||||
name: slotName
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).toBe(`<${tag}>${slot}</${tag}>`)
|
||||
})
|
||||
|
||||
it('render after emit event', async () => {
|
||||
const slot = '<span>Content</span>'
|
||||
const Component = shallowMount(MockComponent, {
|
||||
slots: {
|
||||
default: slot
|
||||
}
|
||||
})
|
||||
const wrapper = shallowMount(BSlotComponent, {
|
||||
propsData: {
|
||||
component: Component.vm
|
||||
}
|
||||
})
|
||||
Component.vm.$emit(defaultEvent, {})
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(wrapper.html()).toBe(`<div>${slot}</div>`)
|
||||
})
|
||||
|
||||
it('refresh after default event (hook)', async () => {
|
||||
const slot = '<span>Content</span>'
|
||||
const Component = shallowMount(MockComponent, {
|
||||
slots: {
|
||||
default: slot
|
||||
}
|
||||
})
|
||||
const refresh = jest.fn()
|
||||
const wrapper = shallowMount(BSlotComponent, {
|
||||
propsData: {
|
||||
component: Component.vm
|
||||
},
|
||||
methods: {
|
||||
refresh
|
||||
}
|
||||
})
|
||||
Component.vm.$forceUpdate()
|
||||
await Component.vm.$nextTick()
|
||||
expect(wrapper.html()).toBe(`<div>${slot}</div>`)
|
||||
})
|
||||
|
||||
it('refresh', () => {
|
||||
const event = 'component-event'
|
||||
const slot = '<span>Content</span>'
|
||||
const Component = shallowMount(MockComponent, {
|
||||
slots: {
|
||||
default: slot
|
||||
}
|
||||
})
|
||||
const refresh = jest.fn()
|
||||
const wrapper = shallowMount(BSlotComponent, {
|
||||
propsData: {
|
||||
component: Component.vm,
|
||||
event
|
||||
},
|
||||
methods: {
|
||||
refresh
|
||||
}
|
||||
})
|
||||
Component.vm.$emit(event, {})
|
||||
expect(refresh).toHaveBeenCalledTimes(1)
|
||||
expect(wrapper.html()).toBe(`<div>${slot}</div>`)
|
||||
})
|
||||
|
||||
it('destroy', () => {
|
||||
const slot = '<span>Content</span>'
|
||||
const Component = shallowMount(MockComponent, {
|
||||
slots: {
|
||||
default: slot
|
||||
}
|
||||
})
|
||||
const refresh = jest.fn()
|
||||
const wrapper = shallowMount(BSlotComponent, {
|
||||
propsData: {
|
||||
component: Component.vm
|
||||
},
|
||||
methods: {
|
||||
refresh
|
||||
}
|
||||
})
|
||||
wrapper.destroy()
|
||||
Component.vm.$emit(defaultEvent, {})
|
||||
expect(refresh).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
})
|
||||
+410
@@ -0,0 +1,410 @@
|
||||
import FormElementMixin from './FormElementMixin'
|
||||
import { isMobile } from './helpers'
|
||||
import config from './config'
|
||||
|
||||
const AM = 'AM'
|
||||
const PM = 'PM'
|
||||
const HOUR_FORMAT_24 = '24'
|
||||
const HOUR_FORMAT_12 = '12'
|
||||
|
||||
export default {
|
||||
mixins: [FormElementMixin],
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
value: Date,
|
||||
inline: Boolean,
|
||||
minTime: Date,
|
||||
maxTime: Date,
|
||||
placeholder: String,
|
||||
editable: Boolean,
|
||||
disabled: Boolean,
|
||||
hourFormat: {
|
||||
type: String,
|
||||
default: HOUR_FORMAT_24,
|
||||
validator: (value) => {
|
||||
return value === HOUR_FORMAT_24 || value === HOUR_FORMAT_12
|
||||
}
|
||||
},
|
||||
incrementMinutes: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
timeFormatter: {
|
||||
type: Function,
|
||||
default: (date) => {
|
||||
this.formatTime(date)
|
||||
}
|
||||
},
|
||||
timeParser: {
|
||||
type: Function,
|
||||
default: (date) => {
|
||||
this.parseTime(date)
|
||||
}
|
||||
},
|
||||
mobileNative: {
|
||||
type: Boolean,
|
||||
default: () => {
|
||||
return config.defaultTimepickerMobileNative
|
||||
}
|
||||
},
|
||||
position: String,
|
||||
unselectableTimes: Array
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dateSelected: this.value,
|
||||
hoursSelected: null,
|
||||
minutesSelected: null,
|
||||
meridienSelected: null,
|
||||
_elementRef: 'input',
|
||||
AM,
|
||||
PM,
|
||||
HOUR_FORMAT_24,
|
||||
HOUR_FORMAT_12
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hours() {
|
||||
const hours = []
|
||||
const numberOfHours = this.isHourFormat24 ? 24 : 12
|
||||
for (let i = 0; i < numberOfHours; i++) {
|
||||
let value = i
|
||||
let label = value
|
||||
if (!this.isHourFormat24) {
|
||||
value = (i + 1)
|
||||
label = value
|
||||
if (this.meridienSelected === this.AM) {
|
||||
if (value === 12) {
|
||||
value = 0
|
||||
}
|
||||
} else if (this.meridienSelected === this.PM) {
|
||||
if (value !== 12) {
|
||||
value += 12
|
||||
}
|
||||
}
|
||||
}
|
||||
hours.push({
|
||||
label: this.formatNumber(label),
|
||||
value: value
|
||||
})
|
||||
}
|
||||
return hours
|
||||
},
|
||||
|
||||
minutes() {
|
||||
const minutes = []
|
||||
for (let i = 0; i < 60; i += this.incrementMinutes) {
|
||||
minutes.push({
|
||||
label: this.formatNumber(i),
|
||||
value: i
|
||||
})
|
||||
}
|
||||
return minutes
|
||||
},
|
||||
meridiens() {
|
||||
return [AM, PM]
|
||||
},
|
||||
|
||||
isMobile() {
|
||||
return this.mobileNative && isMobile.any()
|
||||
},
|
||||
|
||||
isHourFormat24() {
|
||||
return this.hourFormat === HOUR_FORMAT_24
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
||||
hourFormat(value) {
|
||||
if (this.hoursSelected !== null) {
|
||||
this.meridienSelected = this.hoursSelected >= 12 ? PM : AM
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Emit input event with selected date as payload.
|
||||
*/
|
||||
dateSelected(value) {
|
||||
this.$emit('input', value)
|
||||
},
|
||||
|
||||
/**
|
||||
* When v-model is changed:
|
||||
* 1. Update internal value.
|
||||
* 2. If it's invalid, validate again.
|
||||
*/
|
||||
value(value) {
|
||||
this.updateInternalState(value)
|
||||
this.dateSelected = value
|
||||
|
||||
!this.isValid && this.$refs.input.checkHtml5Validity()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onMeridienChange(value) {
|
||||
if (this.hoursSelected !== null) {
|
||||
if (value === PM) {
|
||||
this.hoursSelected += 12
|
||||
} else if (value === AM) {
|
||||
this.hoursSelected -= 12
|
||||
}
|
||||
}
|
||||
this.updateDateSelected(this.hoursSelected, this.minutesSelected, value)
|
||||
},
|
||||
|
||||
onHoursChange(value) {
|
||||
this.updateDateSelected(
|
||||
parseInt(value, 10),
|
||||
this.minutesSelected,
|
||||
this.meridienSelected
|
||||
)
|
||||
},
|
||||
|
||||
onMinutesChange(value) {
|
||||
this.updateDateSelected(
|
||||
this.hoursSelected,
|
||||
parseInt(value, 10),
|
||||
this.meridienSelected
|
||||
)
|
||||
},
|
||||
|
||||
updateDateSelected(hours, minutes, meridiens) {
|
||||
if (hours != null && minutes != null &&
|
||||
((!this.isHourFormat24 && meridiens !== null) || this.isHourFormat24)) {
|
||||
if (this.dateSelected && !isNaN(this.dateSelected)) {
|
||||
this.dateSelected = new Date(this.dateSelected)
|
||||
} else {
|
||||
this.dateSelected = new Date()
|
||||
this.dateSelected.setMilliseconds(0)
|
||||
this.dateSelected.setSeconds(0)
|
||||
}
|
||||
this.dateSelected.setHours(hours)
|
||||
this.dateSelected.setMinutes(minutes)
|
||||
}
|
||||
},
|
||||
|
||||
updateInternalState(value) {
|
||||
if (value) {
|
||||
this.hoursSelected = value.getHours()
|
||||
this.minutesSelected = value.getMinutes()
|
||||
this.meridienSelected = value.getHours() >= 12 ? PM : AM
|
||||
} else {
|
||||
this.hoursSelected = null
|
||||
this.minutesSelected = null
|
||||
this.meridienSelected = AM
|
||||
}
|
||||
},
|
||||
|
||||
isHourDisabled(hour) {
|
||||
let disabled = false
|
||||
if (this.minTime) {
|
||||
const minHours = this.minTime.getHours()
|
||||
disabled = hour < minHours
|
||||
}
|
||||
if (this.maxTime) {
|
||||
if (!disabled) {
|
||||
const maxHours = this.maxTime.getHours()
|
||||
disabled = hour > maxHours
|
||||
}
|
||||
}
|
||||
if (this.unselectableTimes) {
|
||||
if (!disabled) {
|
||||
if (this.minutesSelected !== null) {
|
||||
const unselectable = this.unselectableTimes.filter((time) => {
|
||||
return time.getHours() === hour &&
|
||||
time.getMinutes() === this.minutesSelected
|
||||
})
|
||||
disabled = unselectable.length > 0
|
||||
} else {
|
||||
const unselectable = this.unselectableTimes.filter((time) => {
|
||||
return time.getHours() === hour
|
||||
})
|
||||
disabled = unselectable.length === this.minutes.length
|
||||
}
|
||||
}
|
||||
}
|
||||
return disabled
|
||||
},
|
||||
|
||||
isMinuteDisabled(minute) {
|
||||
let disabled = false
|
||||
if (this.hoursSelected !== null) {
|
||||
if (this.isHourDisabled(this.hoursSelected)) {
|
||||
disabled = true
|
||||
} else {
|
||||
if (this.minTime) {
|
||||
const minHours = this.minTime.getHours()
|
||||
const minMinutes = this.minTime.getMinutes()
|
||||
disabled = this.hoursSelected === minHours && minute < minMinutes
|
||||
}
|
||||
if (this.maxTime) {
|
||||
if (!disabled) {
|
||||
const maxHours = this.maxTime.getHours()
|
||||
const minMinutes = this.maxTime.getMinutes()
|
||||
disabled = this.hoursSelected === maxHours && minute > minMinutes
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.unselectableTimes) {
|
||||
if (!disabled) {
|
||||
const unselectable = this.unselectableTimes.filter((time) => {
|
||||
return time.getHours() === this.hoursSelected &&
|
||||
time.getMinutes() === minute
|
||||
})
|
||||
disabled = unselectable.length > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
return disabled
|
||||
},
|
||||
|
||||
/*
|
||||
* Parse string into date
|
||||
*/
|
||||
onChange(value) {
|
||||
const date = this.parseTime(value)
|
||||
this.updateInternalState(date)
|
||||
if (date && !isNaN(date)) {
|
||||
this.dateSelected = date
|
||||
} else {
|
||||
// Force refresh input value when not valid date
|
||||
this.dateSelected = null
|
||||
this.$refs.input.newValue = this.dateSelected
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Close dropdown time picker
|
||||
*/
|
||||
close() {
|
||||
if (this.$refs.dropdown) {
|
||||
this.$refs.dropdown.isActive = false
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Format date into string 'HH-MM-SS'
|
||||
*/
|
||||
formatHHMMSS(value) {
|
||||
const date = new Date(value)
|
||||
if (value && !isNaN(date)) {
|
||||
const hours = date.getHours()
|
||||
const minutes = date.getMinutes()
|
||||
return this.formatNumber(hours) + ':' +
|
||||
this.formatNumber(minutes, true) + ':00'
|
||||
}
|
||||
return ''
|
||||
},
|
||||
|
||||
/*
|
||||
* Parse time from string
|
||||
*/
|
||||
onChangeNativePicker(event) {
|
||||
const date = event.target.value
|
||||
if (date) {
|
||||
if (this.dateSelected && !isNaN(this.dateSelected)) {
|
||||
this.dateSelected = new Date(this.dateSelected)
|
||||
} else {
|
||||
this.dateSelected = new Date()
|
||||
this.dateSelected.setMilliseconds(0)
|
||||
this.dateSelected.setSeconds(0)
|
||||
}
|
||||
const time = date.split(':')
|
||||
this.dateSelected.setHours(parseInt(time[0], 10))
|
||||
this.dateSelected.setMinutes(parseInt(time[1], 10))
|
||||
} else {
|
||||
this.dateSelected = null
|
||||
}
|
||||
},
|
||||
|
||||
formatNumber(value, isMinute) {
|
||||
return this.isHourFormat24 || isMinute
|
||||
? this.pad(value)
|
||||
: value
|
||||
},
|
||||
|
||||
pad(value) {
|
||||
return (value < 10 ? '0' : '') + value
|
||||
},
|
||||
|
||||
/*
|
||||
* Format date into string
|
||||
*/
|
||||
formatValue(date) {
|
||||
if (date && !isNaN(date)) {
|
||||
return this.formatTime(date)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
},
|
||||
|
||||
formatTime(date) {
|
||||
if (typeof config.defaultTimeFormatter === 'function') {
|
||||
return config.defaultTimeFormatter(date)
|
||||
} else {
|
||||
return this.defaultTimeFormatter(date)
|
||||
}
|
||||
},
|
||||
parseTime(date) {
|
||||
if (typeof config.defaultTimeParser === 'function') {
|
||||
return config.defaultTimeParser(date)
|
||||
} else {
|
||||
return this.defaultTimeParser(date)
|
||||
}
|
||||
},
|
||||
defaultTimeFormatter(date) {
|
||||
let hours = date.getHours()
|
||||
const minutes = date.getMinutes()
|
||||
let period = ''
|
||||
if (this.hourFormat === HOUR_FORMAT_12) {
|
||||
period = ' ' + (hours < 12 ? AM : PM)
|
||||
if (hours > 12) {
|
||||
hours -= 12
|
||||
} else if (hours === 0) {
|
||||
hours = 12
|
||||
}
|
||||
}
|
||||
return this.pad(hours) + ':' + this.pad(minutes) + period
|
||||
},
|
||||
defaultTimeParser(timeString) {
|
||||
if (timeString) {
|
||||
let am = false
|
||||
if (this.hourFormat === HOUR_FORMAT_12) {
|
||||
const dateString12 = timeString.split(' ')
|
||||
timeString = dateString12[0]
|
||||
am = dateString12[1] === AM
|
||||
}
|
||||
const time = timeString.split(':')
|
||||
let hours = parseInt(time[0], 10)
|
||||
const minutes = parseInt(time[1], 10)
|
||||
if (isNaN(hours) || hours < 0 || hours > 23 ||
|
||||
(this.hourFormat === HOUR_FORMAT_12 && (hours < 1 || hours > 12)) ||
|
||||
isNaN(minutes) || minutes < 0 || minutes > 59) {
|
||||
return null
|
||||
}
|
||||
let d = null
|
||||
if (this.dateSelected && !isNaN(this.dateSelected)) {
|
||||
d = new Date(this.dateSelected)
|
||||
} else {
|
||||
d = new Date()
|
||||
d.setMilliseconds(0)
|
||||
d.setSeconds(0)
|
||||
}
|
||||
d.setMinutes(minutes)
|
||||
if (this.hourFormat === HOUR_FORMAT_12) {
|
||||
if (am && hours === 12) {
|
||||
hours = 0
|
||||
} else if (!am && hours !== 12) {
|
||||
hours += 12
|
||||
}
|
||||
}
|
||||
d.setHours(hours)
|
||||
return d
|
||||
}
|
||||
return null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.updateInternalState(this.value)
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
let config = {
|
||||
defaultContainerElement: null,
|
||||
defaultIconPack: 'mdi',
|
||||
defaultIconComponent: null,
|
||||
defaultDialogConfirmText: null,
|
||||
defaultDialogCancelText: null,
|
||||
defaultSnackbarDuration: 3500,
|
||||
defaultSnackbarPosition: null,
|
||||
defaultToastDuration: 2000,
|
||||
defaultToastPosition: null,
|
||||
detaultNotificationDuration: 2000,
|
||||
defaultNotificationPosition: null,
|
||||
defaultTooltipType: 'is-primary',
|
||||
defaultTooltipAnimated: false,
|
||||
defaultInputAutocomplete: 'on',
|
||||
defaultDateFormatter: null,
|
||||
defaultDateParser: null,
|
||||
defaultDateCreator: null,
|
||||
defaultDayNames: null,
|
||||
defaultMonthNames: null,
|
||||
defaultFirstDayOfWeek: null,
|
||||
defaultUnselectableDaysOfWeek: null,
|
||||
defaultTimeFormatter: null,
|
||||
defaultTimeParser: null,
|
||||
defaultModalCanCancel: null,
|
||||
defaultModalScroll: null,
|
||||
defaultDatepickerMobileNative: true,
|
||||
defaultTimepickerMobileNative: true,
|
||||
defaultNoticeQueue: true,
|
||||
defaultInputHasCounter: true,
|
||||
defaultUseHtml5Validation: true
|
||||
}
|
||||
|
||||
export default config
|
||||
|
||||
export const setOptions = (options) => { config = options }
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Get value of an object property/path even if it's nested
|
||||
*/
|
||||
export function getValueByPath(obj, path) {
|
||||
const value = path.split('.').reduce((o, i) => o[i], obj)
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension of indexOf method by equality function if specified
|
||||
*/
|
||||
export function indexOf(array, obj, fn) {
|
||||
if (!array) return -1
|
||||
|
||||
if (!fn || typeof fn !== 'function') return array.indexOf(obj)
|
||||
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
if (fn(array[i], obj)) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
/**
|
||||
* Mobile detection
|
||||
* https://www.abeautifulsite.net/detecting-mobile-devices-with-javascript
|
||||
*/
|
||||
export const isMobile = {
|
||||
Android: function () {
|
||||
return (
|
||||
typeof window !== 'undefined' &&
|
||||
window.navigator.userAgent.match(/Android/i)
|
||||
)
|
||||
},
|
||||
BlackBerry: function () {
|
||||
return (
|
||||
typeof window !== 'undefined' &&
|
||||
window.navigator.userAgent.match(/BlackBerry/i)
|
||||
)
|
||||
},
|
||||
iOS: function () {
|
||||
return (
|
||||
typeof window !== 'undefined' &&
|
||||
window.navigator.userAgent.match(/iPhone|iPad|iPod/i)
|
||||
)
|
||||
},
|
||||
Opera: function () {
|
||||
return (
|
||||
typeof window !== 'undefined' &&
|
||||
window.navigator.userAgent.match(/Opera Mini/i)
|
||||
)
|
||||
},
|
||||
Windows: function () {
|
||||
return (
|
||||
typeof window !== 'undefined' &&
|
||||
window.navigator.userAgent.match(/IEMobile/i)
|
||||
)
|
||||
},
|
||||
any: function () {
|
||||
return (
|
||||
isMobile.Android() ||
|
||||
isMobile.BlackBerry() ||
|
||||
isMobile.iOS() ||
|
||||
isMobile.Opera() ||
|
||||
isMobile.Windows()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function removeElement(el) {
|
||||
if (typeof el.remove !== 'undefined') {
|
||||
el.remove()
|
||||
} else if (typeof el.parentNode !== 'undefined') {
|
||||
el.parentNode.removeChild(el)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape regex characters
|
||||
* http://stackoverflow.com/a/6969486
|
||||
*/
|
||||
export function escapeRegExpChars(value) {
|
||||
if (!value) return value
|
||||
|
||||
// eslint-disable-next-line
|
||||
return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&')
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
|
||||
export const use = (plugin) => {
|
||||
if (typeof window !== 'undefined' && window.Vue) {
|
||||
window.Vue.use(plugin)
|
||||
}
|
||||
}
|
||||
|
||||
export const registerComponent = (Vue, component) => {
|
||||
Vue.component(component.name, component)
|
||||
}
|
||||
|
||||
export const registerComponentProgrammatic = (Vue, property, component) => {
|
||||
Vue.prototype[property] = component
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// Polyfills for SSR
|
||||
|
||||
export const isSSR = typeof window === 'undefined'
|
||||
|
||||
export const HTMLElement = isSSR ? Object : window.HTMLElement
|
||||
export const File = isSSR ? Object : window.File
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import config, { setOptions } from '@utils/config'
|
||||
|
||||
describe('Utils', () => {
|
||||
describe('Test options', () => {
|
||||
test('defaultToastDuration option should be 1000', () => {
|
||||
setOptions(Object.assign(config, {defaultToastDuration: 1000}))
|
||||
expect(config.defaultToastDuration).toBe(1000)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user