node_modules updated, buefy add to lib folder

This commit is contained in:
2019-06-04 14:29:48 +02:00
parent 0a3f6ff240
commit 5d28bc36e1
3957 changed files with 306575 additions and 21738 deletions
+141
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
})
})
})