build version 400

This commit is contained in:
Mohit Panjwani
2020-12-02 17:54:08 +05:30
parent 326508e567
commit 89ee58590c
963 changed files with 62887 additions and 48868 deletions

View File

@ -0,0 +1,157 @@
<template>
<div>
<sw-dropdown :is-show="isShow" variant="search-dropdown">
<sw-input
slot="activator"
v-model="name"
:placeholder="$t('global_search.search')"
variant="search-input"
@input="throttledMethod"
>
<search-icon slot="leftIcon" class="h-5 m-1 text-gray-500" />
<loading-icon
slot="rightIcon"
v-show="isLoading"
class="absolute right-0 h-5 m-1 animate-spin text-primary-400"
/>
</sw-input>
<div class="w-64 h-40 overflow-y-scroll box">
<div v-if="getCustomerList.length > 0 && !isLoading">
<label class="text-xs text-gray-400 uppercase">
{{ $t('global_search.customers') }}
</label>
<router-link
v-for="d in getCustomerList"
:key="d.id"
:to="`/admin/customers/${d.id}/view`"
>
<sw-dropdown-item>
<span
class="flex items-center justify-center w-8 h-8 mr-4 text-xs font-semibold bg-gray-300 rounded-full text-primary-500"
>
{{ initGenerator(d.name) }}
</span>
<div v-if="d.contact_name" class="flex flex-col">
<span class="text-sm text-black">{{ d.name }}</span>
<span class="text-xs text-gray-500">{{ d.contact_name }}</span>
</div>
<div v-else class="flex items-center">
<span class="text-sm text-black">{{ d.name }}</span>
</div>
</sw-dropdown-item>
</router-link>
</div>
<div v-if="getUserList.length > 0 && !isLoading">
<label class="text-xs text-gray-400 uppercase">{{
$t('global_search.users')
}}</label>
<router-link
v-for="d in getUserList"
:key="d.id"
:to="`/admin/users/${d.id}/edit`"
>
<sw-dropdown-item>
<span
class="flex items-center justify-center w-8 h-8 mr-4 text-xs font-semibold bg-gray-300 rounded-full text-primary-500"
>
{{ initGenerator(d.name) }}
</span>
<div class="flex items-center">
<span class="text-sm text-black">{{ d.name }}</span>
</div>
</sw-dropdown-item>
</router-link>
</div>
<div
v-if="
getUserList.length === 0 &&
getCustomerList.length === 0 &&
!isLoading
"
>
<span
class="flex items-center justify-center text-sm font-normal text-gray-500"
>
{{ $t('global_search.no_results_found') }}
</span>
</div>
</div>
</sw-dropdown>
</div>
</template>
<script>
import { SearchIcon } from '@vue-hero-icons/solid'
import { mapActions, mapGetters } from 'vuex'
import LoadingIcon from '../components/icon/LoadingIcon'
import _ from 'lodash'
export default {
components: {
SearchIcon,
LoadingIcon,
},
data() {
return {
isShow: false,
isLoading: false,
name: '',
}
},
computed: {
...mapGetters('search', ['getCustomerList', 'getUserList']),
},
created() {
this.searchUsers()
},
methods: {
...mapActions('search', ['searchUsers']),
throttledMethod: _.debounce(async function () {
this.isLoading = true
await this.searchUsers({ search: this.name }).then(() => {
this.isShow = true
})
if (this.name === '') {
this.isShow = false
}
this.isLoading = false
}, 500),
initGenerator(name) {
if (name) {
let nameSplit = name.split('')
let initials =
nameSplit[0].charAt(0).toUpperCase() +
nameSplit[1].charAt(0).toUpperCase()
return initials
}
},
},
}
</script>
<style scoped>
.box::-webkit-scrollbar {
width: 4px;
}
.box::-webkit-scrollbar-thumb {
background-color: transparent;
outline: 1px solid white;
border-radius: 0.42rem !important;
}
.box::-webkit-scrollbar-thumb {
background-color: #e4e6ef;
}
</style>

View File

@ -1,195 +0,0 @@
<template>
<button
:type="type"
:class="btnClass"
:disabled="disabled || loading"
@click="handleClick"
>
<font-awesome-icon
v-if="icon && !loading && !rightIcon"
:class="iconClass"
:icon="icon"
class="vue-icon icon-left"
/>
<font-awesome-icon
v-if="loading"
:class="iconClass"
icon="spinner"
class="fa-spin"
/>
<slot />
<font-awesome-icon
v-if="icon && !loading && rightIcon"
:class="iconClass"
:icon="icon"
class="vue-icon icon-right"
/>
</button>
</template>
<script>
export default {
props: {
icon: {
type: String,
required: false,
default: ''
},
color: {
type: String,
required: false,
default: ''
},
round: {
type: Boolean,
required: false,
default: false
},
outline: {
type: Boolean,
required: false,
default: false
},
size: {
type: String,
required: false,
default: 'default'
},
loading: {
type: Boolean,
required: false,
default: false
},
block: {
type: Boolean,
required: false,
default: false
},
iconButton: {
type: Boolean,
required: false,
default: false
},
disabled: {
type: Boolean,
required: false,
default: false
},
rightIcon: {
type: Boolean,
required: false,
default: false
},
type: {
type: String,
required: false,
default: 'button'
}
},
computed: {
btnClass () {
if (this.isCustomStyle) {
return ''
}
let btnClass = 'base-button '
switch (this.color) {
case 'success':
if (this.outline) {
btnClass += `btn btn-outline-success `
} else {
btnClass += `btn btn-success `
}
break
case 'danger':
if (this.outline) {
btnClass += `btn btn-outline-danger `
} else {
btnClass += `btn btn-danger `
}
break
case 'warning':
if (this.outline) {
btnClass += `btn btn-outline-warning `
} else {
btnClass += `btn btn-warning `
}
break
case 'info':
if (this.outline) {
btnClass += `btn btn-outline-info `
} else {
btnClass += `btn btn-info `
}
break
case 'theme':
if (this.outline) {
btnClass += `btn btn-outline-primary `
} else {
btnClass += `btn btn-primary `
}
break
case 'theme-light':
if (this.outline) {
btnClass += `btn btn-outline-light `
} else {
btnClass += `btn btn-light `
}
break
default:
if (this.outline) {
btnClass += `btn btn-outline-dark `
} else {
btnClass += `btn btn-dark `
}
break
}
switch (this.size) {
case 'large':
btnClass += 'btn-lg '
break
case 'small':
btnClass += 'btn-sm '
break
default:
btnClass += 'default-size '
}
if (this.block) {
btnClass += 'btn-block '
}
if (this.disabled) {
btnClass += ' btn-cursor-not-allowed'
}
return btnClass
},
iconClass () {
if (this.loading || !this.iconButton) {
if (this.rightIcon) {
return 'ml-2'
}
return 'mr-2'
}
return 'icon-button'
}
},
methods: {
handleClick (e) {
this.$emit('click')
}
}
}
</script>

View File

@ -0,0 +1,302 @@
<template>
<div class="relative">
<div class="absolute bottom-0 right-0 z-10">
<sw-dropdown
:close-on-select="true"
max-height="220"
position="bottom-end"
class="mb-2"
>
<sw-button
slot="activator"
variant="primary-outline"
type="button"
class="mr-2"
>
<plus-sm-icon class="h-5 mr-1 -ml-2" />
{{ $t('settings.customization.addresses.insert_fields') }}
</sw-button>
<div class="flex p-2">
<ul v-for="(type, index) in fieldList" :key="index" class="list-none">
<li class="mb-1 ml-2 text-xs font-semibold text-gray-500 uppercase">
{{ type.label }}
</li>
<li
v-for="(field, index) in type.fields"
:key="index"
class="w-48 text-sm font-normal cursor-pointer hover:bg-gray-200"
@click="insertField(field.value)"
>
<div class="flex">
<chevron-double-right-icon class="h-3 mt-1 text-gray-400" />{{
field.label
}}
</div>
</li>
</ul>
</div>
</sw-dropdown>
</div>
<sw-editor
v-model="inputValue"
:set-editor="inputValue"
:disabled="disabled"
:invalid="isFieldValid"
:placeholder="placeholder"
variant="header-editor"
input-class="border-none"
class="text-area-field"
@input="handleInput"
@change="handleChange"
@keyup="handleKeyupEnter"
/>
</div>
</template>
<script>
import { PlusSmIcon } from '@vue-hero-icons/outline'
import { ChevronDoubleRightIcon } from '@vue-hero-icons/solid'
import { mapActions, mapGetters } from 'vuex'
import customFields from '../../mixins/customFields'
export default {
components: {
PlusSmIcon,
ChevronDoubleRightIcon,
},
props: {
value: {
type: [String, Number, File],
default: '',
},
types: {
type: Array,
default: null,
},
placeholder: {
type: String,
default: '',
},
rows: {
type: String,
default: '10',
},
cols: {
type: String,
default: '30',
},
invalid: {
type: Boolean,
default: false,
},
fields: {
type: Array,
default: null,
},
disabled: {
type: Boolean,
default: false,
},
},
data() {
return {
fieldList: [],
invoiceFields: [],
estimateFields: [],
paymentFields: [],
customerFields: [],
position: null,
inputValue: this.value,
}
},
computed: {
...mapGetters('customFields', ['getCustomFields']),
isFieldValid() {
return this.invalid
},
},
watch: {
value() {
this.inputValue = this.value
},
fields() {
if (this.fields && this.fields.length > 0) {
this.getFields()
}
},
getCustomFields(newValue) {
this.invoiceFields = newValue
? newValue.filter((field) => field.model_type === 'Invoice')
: []
this.customerFields = newValue
? newValue.filter((field) => field.model_type === 'Customer')
: []
this.paymentFields = newValue
? newValue.filter((field) => field.model_type === 'Payment')
: []
this.estimateFields = newValue.filter(
(field) => field.model_type === 'Estimate'
)
this.getFields()
},
},
async mounted() {
this.getFields()
await this.fetchNoteCustomFields({ limit: 'all' })
},
methods: {
...mapActions('customFields', ['fetchNoteCustomFields']),
getFields() {
this.fieldList = []
if (this.fields && this.fields.length > 0) {
if (this.fields.find((field) => field == 'shipping')) {
this.fieldList.push({
label: 'Shipping Address',
fields: [
{ label: 'Address name', value: 'SHIPPING_ADDRESS_NAME' },
{ label: 'Country', value: 'SHIPPING_COUNTRY' },
{ label: 'State', value: 'SHIPPING_STATE' },
{ label: 'City', value: 'SHIPPING_CITY' },
{ label: 'Address Street 1', value: 'SHIPPING_ADDRESS_STREET_1' },
{ label: 'Address Street 2', value: 'SHIPPING_ADDRESS_STREET_2' },
{ label: 'Phone', value: 'SHIPPING_PHONE' },
{ label: 'Zip Code', value: 'SHIPPING_ZIP_CODE' },
],
})
}
if (this.fields.find((field) => field == 'billing')) {
this.fieldList.push({
label: 'Billing Address',
fields: [
{ label: 'Address name', value: 'BILLING_ADDRESS_NAME' },
{ label: 'Country', value: 'BILLING_COUNTRY' },
{ label: 'State', value: 'BILLING_STATE' },
{ label: 'City', value: 'BILLING_CITY' },
{ label: 'Address Street 1', value: 'BILLING_ADDRESS_STREET_1' },
{ label: 'Address Street 2', value: 'BILLING_ADDRESS_STREET_2' },
{ label: 'Phone', value: 'BILLING_PHONE' },
{ label: 'Zip Code', value: 'BILLING_ZIP_CODE' },
],
})
}
if (this.fields.find((field) => field == 'customer')) {
this.fieldList.push({
label: 'Customer',
fields: [
{ label: 'Display Name', value: 'CONTACT_DISPLAY_NAME' },
{ label: 'Contact Name', value: 'PRIMARY_CONTACT_NAME' },
{ label: 'Email', value: 'CONTACT_EMAIL' },
{ label: 'Phone', value: 'CONTACT_PHONE' },
{ label: 'Website', value: 'CONTACT_WEBSITE' },
...this.customerFields.map((i) => ({
label: i.label,
value: i.slug,
})),
],
})
}
if (this.fields.find((field) => field == 'invoice')) {
this.fieldList.push({
label: 'Invoice',
fields: [
{ label: 'Date', value: 'INVOICE_DATE' },
{ label: 'Due Date', value: 'INVOICE_DUE_DATE' },
{ label: 'Number', value: 'INVOICE_NUMBER' },
{ label: 'Ref Number', value: 'INVOICE_REF_NUMBER' },
{ label: 'Invoice Link', value: 'INVOICE_LINK' },
...this.invoiceFields.map((i) => ({
label: i.label,
value: i.slug,
})),
],
})
}
if (this.fields.find((field) => field == 'estimate')) {
this.fieldList.push({
label: 'Estimate',
fields: [
{ label: 'Date', value: 'ESTIMATE_DATE' },
{ label: 'Expiry Date', value: 'ESTIMATE_EXPIRY_DATE' },
{ label: 'Number', value: 'ESTIMATE_NUMBER' },
{ label: 'Ref Number', value: 'ESTIMATE_REF_NUMBER' },
{ label: 'Estimate Link', value: 'ESTIMATE_LINK' },
...this.estimateFields.map((i) => ({
label: i.label,
value: i.slug,
})),
],
})
}
if (this.fields.find((field) => field == 'payment')) {
this.fieldList.push({
label: 'Payment',
fields: [
{ label: 'Date', value: 'PAYMENT_DATE' },
{ label: 'Number', value: 'PAYMENT_NUMBER' },
{ label: 'Mode', value: 'PAYMENT_MODE' },
{ label: 'Amount', value: 'PAYMENT_AMOUNT' },
{ label: 'Payment Link', value: 'PAYMENT_LINK' },
...this.paymentFields.map((i) => ({
label: i.label,
value: i.slug,
})),
],
})
}
if (this.fields.find((field) => field == 'company')) {
this.fieldList.push({
label: 'Company',
fields: [
{ label: 'Company Name', value: 'COMPANY_NAME' },
{ label: 'Country', value: 'COMPANY_COUNTRY' },
{ label: 'State', value: 'COMPANY_STATE' },
{ label: 'City', value: 'COMPANY_CITY' },
{ label: 'Address Street 1', value: 'COMPANY_ADDRESS_STREET_1' },
{ label: 'Address Street 2', value: 'COMPANY_ADDRESS_STREET_2' },
{ label: 'Phone', value: 'COMPANY_PHONE' },
{ label: 'Zip Code', value: 'COMPANY_ZIP_CODE' },
],
})
}
}
},
insertField(varName) {
if (this.inputValue) {
this.inputValue += `{${varName}}`
} else {
this.inputValue = `{${varName}}`
}
this.$emit('input', this.inputValue)
},
handleInput(e) {
this.$emit('input', this.inputValue)
},
handleChange(e) {
this.$emit('change', this.inputValue)
},
handleKeyupEnter(e) {
this.$emit('keyup', this.inputValue)
},
handleKeyDownEnter(e) {
this.$emit('keydown', e, this.inputValue)
},
handleFocusOut(e) {
this.$emit('blur', this.inputValue)
},
},
}
</script>

View File

@ -1,6 +1,6 @@
<template>
<div class="item-selector">
<base-select
<sw-select
ref="baseSelect"
v-model="customerSelect"
:options="customers"
@ -21,40 +21,41 @@
import { mapActions, mapGetters } from 'vuex'
export default {
data () {
data() {
return {
customerSelect: null,
loading: false
loading: false,
}
},
computed: {
...mapGetters('customer', [
'customers'
])
...mapGetters('customer', ['customers']),
},
created() {
this.fetchCustomers()
},
methods: {
...mapActions('customer', [
'fetchCustomers'
]),
async searchCustomers (search) {
...mapActions('customer', ['fetchCustomers']),
async searchCustomers(search) {
this.loading = true
await this.fetchCustomers({search})
await this.fetchCustomers({ search })
this.loading = false
},
onTextChange (val) {
onTextChange(val) {
this.searchCustomers(val)
},
checkCustomers (val) {
checkCustomers(val) {
if (!this.customers.length) {
this.fetchCustomers()
}
},
deselectCustomer () {
deselectCustomer() {
this.customerSelect = null
this.$emit('deselect')
}
}
},
},
}
</script>

View File

@ -0,0 +1,142 @@
<template>
<div>
<sw-date-picker
ref="BaseDatepicker"
v-model="date"
:config="config"
:placeholder="placeholder"
:disabled="disabled"
:invalid="invalid"
:name="name"
:tabindex="tabindex"
@input="onDateChange"
/>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import SwDatePicker from '@bytefury/spacewind/src/components/SwDatePicker'
import moment from 'moment'
const fromMomentDate = (date, format = 'YYYY-MM-DD') =>
moment(new Date(date), format)
export default {
components: {
SwDatePicker,
},
props: {
placeholder: {
type: String,
default: null,
},
invalid: {
type: Boolean,
default: false,
},
enableTime: {
type: Boolean,
default: false,
},
time_24hr: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
name: {
type: String,
default: null,
},
value: {
type: [String, Date],
default: () => moment(new Date()),
},
tabindex: {
type: Number,
default: null,
},
},
data() {
return {
date: null,
config: {
altInput: true,
enableTime: this.enableTime,
time_24hr: this.time_24hr,
},
}
},
computed: {
...mapGetters('company', {
carbonFormat: 'getCarbonDateFormat',
momentFormat: 'getMomentDateFormat',
}),
},
watch: {
value(val) {
if (val && !this.enableTime) {
this.date = fromMomentDate(val, 'YYYY-MM-DD').format('YYYY-MM-DD')
} else {
this.date = fromMomentDate(val, 'YYYY-MM-DD').format('YYYY-MM-DD H:m:s')
}
},
enableTime(val) {
this.$set(this.config, 'enableTime', this.enableTime)
},
carbonFormat() {
if (!this.enableTime) {
this.$set(
this.config,
'altFormat',
this.carbonFormat ? this.carbonFormat : 'd M Y'
)
} else {
this.$set(
this.config,
'altFormat',
this.carbonFormat ? `${this.carbonFormat} H:i ` : 'd M Y H:i'
)
}
},
},
mounted() {
this.$set(this.config, 'enableTime', this.enableTime)
if (!this.enableTime) {
this.$set(
this.config,
'altFormat',
this.carbonFormat ? this.carbonFormat : 'd M Y'
)
} else {
this.$set(
this.config,
'altFormat',
this.carbonFormat ? `${this.carbonFormat} H:i ` : 'd M Y H:i'
)
}
if (this.value && !this.enableTime) {
this.date = fromMomentDate(this.value, 'YYYY-MM-DD').format('YYYY-MM-DD')
return true
}
if (this.value) {
this.date = fromMomentDate(this.value, 'YYYY-MM-DD').format(
'YYYY-MM-DD HH:mm:SS'
)
}
},
methods: {
onDateChange(date) {
this.$emit('input', date)
this.$emit('change', date)
},
},
}
</script>
<style lang="scss">
.flatpickr-calendar.open {
z-index: 60 !important;
}
</style>

View File

@ -1,158 +0,0 @@
<template>
<div class="base-input">
<font-awesome-icon v-if="icon && isAlignLeftIcon" :icon="icon" class="left-icon"/>
<input
ref="baseInput"
v-model="inputValue"
:type="toggleType"
:disabled="disabled"
:readonly="readOnly"
:name="name"
:tabindex="tabIndex"
:class="[{ 'input-field-left-icon': icon && isAlignLeftIcon, 'input-field-right-icon': (icon && !isAlignLeftIcon) || isInputGroup, invalid: isFieldValid, disabled: disabled, 'small-input': small}, inputClass]"
:placeholder="placeholder"
:autocomplete="autocomplete"
class="input-field"
@input="handleInput"
@change="handleChange"
@keyup="handleKeyupEnter"
@keydown="handleKeyDownEnter"
@blur="handleFocusOut"
>
<div v-if="showPassword && isAlignLeftIcon" style="cursor: pointer" @click="showPass = !showPass" >
<font-awesome-icon :icon="!showPass ?'eye': 'eye-slash'" class="right-icon" />
</div>
<font-awesome-icon v-if="icon && !isAlignLeftIcon" :icon="icon" class="right-icon" />
<span v-if="isInputGroup" class="right-input-group-text">
{{ inputGroupText }}
</span>
</div>
</template>
<script>
export default {
props: {
name: {
type: String,
default: ''
},
type: {
type: String,
default: 'text'
},
tabIndex: {
type: String,
default: ''
},
value: {
type: [String, Number, File],
default: ''
},
placeholder: {
type: String,
default: ''
},
invalid: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
readOnly: {
type: Boolean,
default: false
},
icon: {
type: String,
default: ''
},
inputClass: {
type: String,
default: ''
},
small: {
type: Boolean,
default: false
},
alignIcon: {
type: String,
default: 'left'
},
autocomplete: {
type: String,
default: 'on'
},
showPassword: {
type: Boolean,
default: false
},
isInputGroup: {
type: Boolean,
default: false,
},
inputGroupText: {
type: String,
default: null,
}
},
data () {
return {
inputValue: this.value,
focus: false,
showPass: false
}
},
computed: {
isFieldValid () {
return this.invalid
},
isAlignLeftIcon () {
if (this.alignIcon === 'left') {
return true
}
return false
},
toggleType () {
if (this.showPass) {
return 'text'
}
return this.type
}
},
watch: {
'value' () {
this.inputValue = this.value
},
focus () {
this.focusInput()
}
},
mounted () {
this.focusInput()
},
methods: {
focusInput () {
if (this.focus) {
this.$refs.baseInput.focus()
}
},
handleInput (e) {
this.$emit('input', this.inputValue)
},
handleChange (e) {
this.$emit('change', this.inputValue)
},
handleKeyupEnter (e) {
this.$emit('keyup', this.inputValue)
},
handleKeyDownEnter (e) {
this.$emit('keydown', e, this.inputValue)
},
handleFocusOut (e) {
this.$emit('blur', this.inputValue)
}
}
}
</script>

View File

@ -1,8 +1,12 @@
<template>
<div class="base-loader">
<div class="spinner"/>
<div class="overlay">
<div class="loader-inner ball-scale-ripple-multiple">
<div
:class="{ 'bg-gray-400': showBgOverlay }"
class="absolute top-0 left-0 z-20 flex items-center justify-center w-full h-full bg-opacity-25 base-loader"
>
<div class="absolute top-0 left-0 w-full h-full overlay">
<div
class="absolute flex items-center justify-center ball-scale-ripple-multiple"
>
<div></div>
<div></div>
<div></div>
@ -10,19 +14,110 @@
</div>
</div>
</template>
<script>
export default {
props: {
showBgOverlay: {
default: false,
type: Boolean,
},
},
}
</script>
<style scoped>
.overlay {
height: 100%;
width: 100%;
background: rgba(255,255,255,0.4);
position: absolute;
top: 7%;
left: 13%;
<style lang="scss">
// function.scss
@function delay($interval, $count, $index) {
@return ($index * $interval) - ($interval * $count);
}
// mixins.scss
@mixin global-bg() {
background-color: #5851d8;
}
@mixin global-animation() {
animation-fill-mode: both;
}
@mixin balls() {
@include global-bg();
width: 15px;
height: 15px;
border-radius: 100%;
margin: 2px;
}
@mixin lines() {
@include global-bg();
width: $line-width;
height: $line-height;
border-radius: 2px;
margin: $margin;
}
.base-loader {
.overlay {
@keyframes ball-scale-ripple-multiple {
0% {
transform: scale(0.1);
opacity: 1;
}
70% {
transform: scale(1);
opacity: 0.7;
}
100% {
opacity: 0;
}
}
@mixin ball-scale-ripple-multiple($n: 3, $start: 0) {
@for $i from $start through $n {
> div:nth-child(#{$i}) {
animation-delay: delay(0.2s, $n, $i - 1);
}
}
}
.loader {
width: 100%;
position: relative;
min-height: 500px;
}
.ball-scale-ripple-multiple {
transform: translateY(-25px);
top: 50%;
left: 50%;
@include ball-scale-ripple-multiple();
transform: translateY(-50px / 2);
> div {
@include global-animation();
position: absolute;
top: -2px;
left: -26px;
width: 50px;
height: 50px;
border-radius: 100%;
border: 2px solid #5851d8;
animation: ball-scale-ripple-multiple 1.25s 0s infinite
cubic-bezier(0.21, 0.53, 0.56, 0.8);
}
}
}
&.table-loader .overlay {
background: rgba(255, 255, 255, 0.5);
height: calc(100% - 80px);
top: 80px;
}
}
</style>

View File

@ -0,0 +1,7 @@
<template>
<main
class="flex flex-col flex-1 p-8 overflow-y-auto border-b border-gray-300 border-solid"
>
<slot />
</main>
</template>

View File

@ -1,71 +0,0 @@
<template>
<div class="base-prefix-input" @click="focusInput">
<font-awesome-icon v-if="icon" :icon="icon" class="icon" />
<p class="prefix-label"><span class="mr-1">{{ prefix }}</span>-</p>
<input
ref="basePrefixInput"
v-model="inputValue"
:type="type"
class="prefix-input-field"
@input="handleInput"
@change="handleChange"
@keyup="handleKeyupEnter"
@keydown="handleKeyDownEnter"
@blur="handleFocusOut"
>
</div>
</template>
<script>
export default {
props: {
prefix: {
type: String,
default: null,
required: true
},
icon: {
type: String,
default: null
},
value: {
type: [String, Number, File],
default: ''
},
type: {
type: String,
default: 'text'
}
},
data () {
return {
inputValue: this.value
}
},
watch: {
'value' () {
this.inputValue = this.value
}
},
methods: {
focusInput () {
this.$refs.basePrefixInput.focus()
},
handleInput (e) {
this.$emit('input', this.inputValue)
},
handleChange (e) {
this.$emit('change', this.inputValue)
},
handleKeyupEnter (e) {
this.$emit('keyup', this.inputValue)
},
handleKeyDownEnter (e) {
this.$emit('keydown', e, this.inputValue)
},
handleFocusOut (e) {
this.$emit('blur', this.inputValue)
}
}
}
</script>

View File

@ -1,66 +0,0 @@
<template>
<div class="base-switch">
<input
:id="uniqueId"
v-model="checkValue"
type="checkbox"
@input="handleInput"
@change="handleChange"
@keyup="handleKeyupEnter"
@blur="handleFocusOut"
>
<label class="switch-label" :for="uniqueId"/>
</div>
</template>
<script>
export default {
props: {
value: {
type: Boolean,
required: false,
default: false
},
disabled: {
type: Boolean,
required: false,
default: false
}
},
data () {
return {
id: null,
checkValue: this.value
}
},
computed: {
uniqueId () {
return '_' + Math.random().toString(36).substr(2, 9)
}
},
watch: {
'value' () {
this.checkValue = this.value
}
},
methods: {
handleInput (e) {
this.$emit('input', e.target.checked)
},
handleChange (e) {
this.$emit('change', this.checkValue)
},
handleKeyupEnter (e) {
this.$emit('keyup', this.checkValue)
},
handleFocusOut (e) {
this.$emit('blur', this.checkValue)
}
}
}
</script>
<style scoped>
/* .switch-label {
margin-bottom: 3px !important
} */
</style>

View File

@ -1,79 +0,0 @@
<template>
<textarea
v-model="inputValue"
:rows="rows"
:cols="cols"
:disabled="disabled"
:class="['base-text-area',{'invalid': isFieldValid, 'disabled': disabled}, inputClass]"
:placeholder="placeholder"
class="text-area-field"
@input="handleInput"
@change="handleChange"
@keyup="handleKeyupEnter"
/>
</template>
<script>
export default {
props: {
rows: {
type: String,
default: '4'
},
cols: {
type: String,
default: '10'
},
value: {
type: String,
default: ''
},
placeholder: {
type: String,
default: ''
},
invalid: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
icon: {
type: String,
default: ''
},
inputClass: {
type: String,
default: ''
}
},
data () {
return {
inputValue: this.value
}
},
computed: {
isFieldValid () {
return this.invalid
}
},
watch: {
'value' () {
this.inputValue = this.value
}
},
methods: {
handleInput (e) {
this.$emit('input', this.inputValue)
},
handleChange (e) {
this.$emit('change', this.inputValue)
},
handleKeyupEnter (e) {
this.$emit('keyup', this.inputValue)
}
}
}
</script>

View File

@ -0,0 +1,81 @@
<template>
<sw-date-picker
ref="BaseDatepicker"
v-model="time"
:config="config"
:placeholder="placeholder"
:disabled="disabled"
:invalid="invalid"
:name="name"
:tabindex="tabindex"
@input="onDateChange"
/>
</template>
<script>
import SwDatePicker from '@bytefury/spacewind/src/components/SwDatePicker'
export default {
components: {
SwDatePicker,
},
props: {
invalid: {
type: Boolean,
default: false,
require: false,
},
defaultTime: {
type: String,
default: null,
require: false,
},
hideClearButton: {
type: String,
default: null,
require: false,
},
value: {
type: [String, Object],
default: '',
},
placeholder: {
type: String,
default: 'HH:mm:ss',
},
tabindex: {
type: Number,
default: null,
},
disabled: {
type: Boolean,
default: false,
},
name: {
type: String,
default: null,
},
},
data() {
return {
time: this.value,
config: {
enableTime: true,
noCalendar: true,
dateFormat: 'H:i',
time_24hr: true,
},
}
},
watch: {
value() {
this.time = this.value
},
},
methods: {
onDateChange(date) {
this.$emit('input', date)
this.$emit('change', date)
},
},
}
</script>

View File

@ -1,649 +0,0 @@
<template>
<div :class="[wrapperClass, isRtl ? 'rtl' : '']" class="base-date-input">
<date-input
:selected-date="selectedDate"
:reset-typed-date="resetTypedDate"
:format="customFormatter"
:translation="translation"
:inline="inline"
:id="id"
:name="name"
:ref-name="refName"
:open-date="openDate"
:placeholder="placeholder"
:input-class="inputClass"
:typeable="typeable"
:clear-button="clearButton"
:clear-button-icon="clearButtonIcon"
:calendar-button="calendarButton"
:calendar-button-icon="calendarButtonIcon"
:calendar-button-icon-content="calendarButtonIconContent"
:disabled="disabled"
:required="required"
:class="{'required-date': invalid}"
:bootstrap-styling="bootstrapStyling"
:use-utc="useUtc"
@showCalendar="showCalendar"
@closeCalendar="close"
@typedDate="setTypedDate"
@clearDate="clearDate">
<slot slot="afterDateInput" name="afterDateInput"/>
</date-input>
<!-- Day View -->
<picker-day
v-if="allowedToShowView('day')"
:page-date="pageDate"
:selected-date="selectedDate"
:show-day-view="showDayView"
:full-month-name="fullMonthName"
:allowed-to-show-view="allowedToShowView"
:disabled-dates="disabledDates"
:highlighted="highlighted"
:calendar-class="calendarClass"
:calendar-style="calendarStyle"
:translation="translation"
:page-timestamp="pageTimestamp"
:is-rtl="isRtl"
:monday-first="mondayFirst"
:day-cell-content="dayCellContent"
:use-utc="useUtc"
@changedMonth="handleChangedMonthFromDayPicker"
@selectDate="selectDate"
@showMonthCalendar="showMonthCalendar"
@selectedDisabled="selectDisabledDate">
<slot slot="beforeCalendarHeader" name="beforeCalendarHeader"/>
</picker-day>
<!-- Month View -->
<picker-month
v-if="allowedToShowView('month')"
:page-date="pageDate"
:selected-date="selectedDate"
:show-month-view="showMonthView"
:allowed-to-show-view="allowedToShowView"
:disabled-dates="disabledDates"
:calendar-class="calendarClass"
:calendar-style="calendarStyle"
:translation="translation"
:is-rtl="isRtl"
:use-utc="useUtc"
@selectMonth="selectMonth"
@showYearCalendar="showYearCalendar"
@changedYear="setPageDate">
<slot slot="beforeCalendarHeader" name="beforeCalendarHeader"/>
</picker-month>
<!-- Year View -->
<picker-year
v-if="allowedToShowView('year')"
:page-date="pageDate"
:selected-date="selectedDate"
:show-year-view="showYearView"
:allowed-to-show-view="allowedToShowView"
:disabled-dates="disabledDates"
:calendar-class="calendarClass"
:calendar-style="calendarStyle"
:translation="translation"
:is-rtl="isRtl"
:use-utc="useUtc"
@selectYear="selectYear"
@changedDecade="setPageDate">
<slot slot="beforeCalendarHeader" name="beforeCalendarHeader"/>
</picker-year>
</div>
</template>
<script>
import en from './src/locale/translations/en'
import DateInput from './DateInput'
import PickerDay from './PickerDay.vue'
import PickerMonth from './PickerMonth.vue'
import PickerYear from './PickerYear.vue'
import utils, { makeDateUtils } from './src/DateUtils'
import { mapGetters } from 'vuex'
import moment from 'moment'
export default {
components: {
DateInput,
PickerDay,
PickerMonth,
PickerYear
},
props: {
value: {
validator: val => utils.validateDateInput(val)
},
name: String,
refName: String,
id: String,
// format: {
// type: [String, Function],
// default: 'dd MMM yyyy'
// },
language: {
type: Object,
default: () => en
},
openDate: {
validator: val => utils.validateDateInput(val)
},
dayCellContent: Function,
fullMonthName: Boolean,
disabledDates: Object,
highlighted: Object,
placeholder: String,
inline: Boolean,
calendarClass: [String, Object, Array],
inputClass: [String, Object, Array],
wrapperClass: [String, Object, Array],
mondayFirst: Boolean,
clearButton: Boolean,
clearButtonIcon: String,
calendarButton: Boolean,
calendarButtonIcon: String,
calendarButtonIconContent: String,
bootstrapStyling: Boolean,
initialView: String,
disabled: Boolean,
required: Boolean,
invalid: Boolean,
typeable: Boolean,
useUtc: Boolean,
minimumView: {
type: String,
default: 'day'
},
maximumView: {
type: String,
default: 'year'
}
},
data () {
const startDate = this.openDate ? new Date(this.openDate) : new Date()
const constructedDateUtils = makeDateUtils(this.useUtc)
const pageTimestamp = constructedDateUtils.setDate(startDate, 1)
return {
/*
* Vue cannot observe changes to a Date Object so date must be stored as a timestamp
* This represents the first day of the current viewing month
* {Number}
*/
pageTimestamp,
/*
* Selected Date
* {Date}
*/
selectedDate: null,
/*
* Flags to show calendar views
* {Boolean}
*/
showDayView: false,
showMonthView: false,
showYearView: false,
/*
* Positioning
*/
calendarHeight: 0,
resetTypedDate: new Date(),
utils: constructedDateUtils
}
},
watch: {
value (value) {
this.setValue(value)
},
openDate () {
this.setPageDate()
},
initialView () {
this.setInitialView()
}
},
computed: {
...mapGetters('preferences', {
'format': 'getMomentDateFormat'
}),
customFormatter () {
let newDate = new Date(this.value)
return moment(newDate).format(this.format)
},
computedInitialView () {
if (!this.initialView) {
return this.minimumView
}
return this.initialView
},
pageDate () {
return new Date(this.pageTimestamp)
},
translation () {
return this.language
},
calendarStyle () {
return {
position: this.isInline ? 'static' : undefined
}
},
isOpen () {
return this.showDayView || this.showMonthView || this.showYearView
},
isInline () {
return !!this.inline
},
isRtl () {
return this.translation.rtl === true
}
},
mounted () {
this.init()
},
methods: {
/**
* Called in the event that the user navigates to date pages and
* closes the picker without selecting a date.
*/
resetDefaultPageDate () {
if (this.selectedDate === null) {
this.setPageDate()
return
}
this.setPageDate(this.selectedDate)
},
/**
* Effectively a toggle to show/hide the calendar
* @return {mixed}
*/
showCalendar () {
if (this.disabled || this.isInline) {
return false
}
if (this.isOpen) {
return this.close(true)
}
this.setInitialView()
},
/**
* Sets the initial picker page view: day, month or year
*/
setInitialView () {
const initialView = this.computedInitialView
if (!this.allowedToShowView(initialView)) {
throw new Error(`initialView '${this.initialView}' cannot be rendered based on minimum '${this.minimumView}' and maximum '${this.maximumView}'`)
}
switch (initialView) {
case 'year':
this.showYearCalendar()
break
case 'month':
this.showMonthCalendar()
break
default:
this.showDayCalendar()
break
}
},
/**
* Are we allowed to show a specific picker view?
* @param {String} view
* @return {Boolean}
*/
allowedToShowView (view) {
const views = ['day', 'month', 'year']
const minimumViewIndex = views.indexOf(this.minimumView)
const maximumViewIndex = views.indexOf(this.maximumView)
const viewIndex = views.indexOf(view)
return viewIndex >= minimumViewIndex && viewIndex <= maximumViewIndex
},
/**
* Show the day picker
* @return {Boolean}
*/
showDayCalendar () {
if (!this.allowedToShowView('day')) {
return false
}
this.close()
this.showDayView = true
return true
},
/**
* Show the month picker
* @return {Boolean}
*/
showMonthCalendar () {
if (!this.allowedToShowView('month')) {
return false
}
this.close()
this.showMonthView = true
return true
},
/**
* Show the year picker
* @return {Boolean}
*/
showYearCalendar () {
if (!this.allowedToShowView('year')) {
return false
}
this.close()
this.showYearView = true
return true
},
/**
* Set the selected date
* @param {Number} timestamp
*/
setDate (timestamp) {
const date = new Date(timestamp)
this.selectedDate = date
this.setPageDate(date)
this.$emit('selected', date)
this.$emit('input', date)
},
/**
* Clear the selected date
*/
clearDate () {
this.selectedDate = null
this.setPageDate()
this.$emit('selected', null)
this.$emit('input', null)
this.$emit('cleared')
},
/**
* @param {Object} date
*/
selectDate (date) {
this.setDate(date.timestamp)
if (!this.isInline) {
this.close(true)
}
this.resetTypedDate = new Date()
},
/**
* @param {Object} date
*/
selectDisabledDate (date) {
this.$emit('selectedDisabled', date)
},
/**
* @param {Object} month
*/
selectMonth (month) {
const date = new Date(month.timestamp)
if (this.allowedToShowView('day')) {
this.setPageDate(date)
this.$emit('changedMonth', month)
this.showDayCalendar()
} else {
this.selectDate(month)
}
},
/**
* @param {Object} year
*/
selectYear (year) {
const date = new Date(year.timestamp)
if (this.allowedToShowView('month')) {
this.setPageDate(date)
this.$emit('changedYear', year)
this.showMonthCalendar()
} else {
this.selectDate(year)
}
},
/**
* Set the datepicker value
* @param {Date|String|Number|null} date
*/
setValue (date) {
if (typeof date === 'string' || typeof date === 'number') {
let parsed = new Date(date)
date = isNaN(parsed.valueOf()) ? null : parsed
}
if (!date) {
this.setPageDate()
this.selectedDate = null
return
}
this.selectedDate = date
this.setPageDate(date)
},
/**
* Sets the date that the calendar should open on
*/
setPageDate (date) {
if (!date) {
if (this.openDate) {
date = new Date(this.openDate)
} else {
date = new Date()
}
}
this.pageTimestamp = this.utils.setDate(new Date(date), 1)
},
/**
* Handles a month change from the day picker
*/
handleChangedMonthFromDayPicker (date) {
this.setPageDate(date)
this.$emit('changedMonth', date)
},
/**
* Set the date from a typedDate event
*/
setTypedDate (date) {
this.setDate(date.getTime())
},
/**
* Close all calendar layers
* @param {Boolean} emitEvent - emit close event
*/
close (emitEvent) {
this.showDayView = this.showMonthView = this.showYearView = false
if (!this.isInline) {
if (emitEvent) {
this.$emit('closed')
}
document.removeEventListener('click', this.clickOutside, false)
}
},
/**
* Initiate the component
*/
init () {
if (this.value) {
this.setValue(this.value)
}
if (this.isInline) {
this.setInitialView()
}
}
},
}
// eslint-disable-next-line
;
</script>
<style lang="css">
.rtl {
direction: rtl;
}
.required-date {
border: 1px solid #FB7178;
border-radius: 5px;
}
.vdp-datepicker {
position: relative;
text-align: left;
}
.vdp-datepicker * {
box-sizing: border-box;
}
.vdp-datepicker__calendar {
position: absolute;
z-index: 100;
background: #fff;
width: 300px;
border: 1px solid #ccc;
}
.vdp-datepicker__calendar header {
display: block;
line-height: 40px;
}
.vdp-datepicker__calendar header span {
display: inline-block;
text-align: center;
width: 71.42857142857143%;
float: left;
}
.vdp-datepicker__calendar header .prev,
.vdp-datepicker__calendar header .next {
width: 14.285714285714286%;
float: left;
text-indent: -10000px;
position: relative;
}
.vdp-datepicker__calendar header .prev:after,
.vdp-datepicker__calendar header .next:after {
content: '';
position: absolute;
left: 50%;
top: 50%;
transform: translateX(-50%) translateY(-50%);
border: 6px solid transparent;
}
.vdp-datepicker__calendar header .prev:after {
border-right: 10px solid #000;
margin-left: -5px;
}
.vdp-datepicker__calendar header .prev.disabled:after {
border-right: 10px solid #ddd;
}
.vdp-datepicker__calendar header .next:after {
border-left: 10px solid #000;
margin-left: 5px;
}
.vdp-datepicker__calendar header .next.disabled:after {
border-left: 10px solid #ddd;
}
.vdp-datepicker__calendar header .prev:not(.disabled),
.vdp-datepicker__calendar header .next:not(.disabled),
.vdp-datepicker__calendar header .up:not(.disabled) {
cursor: pointer;
}
.vdp-datepicker__calendar header .prev:not(.disabled):hover,
.vdp-datepicker__calendar header .next:not(.disabled):hover,
.vdp-datepicker__calendar header .up:not(.disabled):hover {
background: #eee;
}
.vdp-datepicker__calendar .disabled {
color: #ddd;
cursor: default;
}
.vdp-datepicker__calendar .flex-rtl {
display: flex;
width: inherit;
flex-wrap: wrap;
}
.vdp-datepicker__calendar .cell {
display: inline-block;
padding: 0 5px;
width: 14.285714285714286%;
height: 40px;
line-height: 40px;
text-align: center;
vertical-align: middle;
border: 1px solid transparent;
}
.vdp-datepicker__calendar .cell:not(.blank):not(.disabled).day,
.vdp-datepicker__calendar .cell:not(.blank):not(.disabled).month,
.vdp-datepicker__calendar .cell:not(.blank):not(.disabled).year {
cursor: pointer;
}
.vdp-datepicker__calendar .cell:not(.blank):not(.disabled).day:hover,
.vdp-datepicker__calendar .cell:not(.blank):not(.disabled).month:hover,
.vdp-datepicker__calendar .cell:not(.blank):not(.disabled).year:hover {
border: 1px solid #4bd;
}
.vdp-datepicker__calendar .cell.selected {
background: #4bd;
}
.vdp-datepicker__calendar .cell.selected:hover {
background: #4bd;
}
.vdp-datepicker__calendar .cell.selected.highlighted {
background: #4bd;
}
.vdp-datepicker__calendar .cell.highlighted {
background: #cae5ed;
}
.vdp-datepicker__calendar .cell.highlighted.disabled {
color: #a3a3a3;
}
.vdp-datepicker__calendar .cell.grey {
color: #888;
}
.vdp-datepicker__calendar .cell.grey:hover {
background: inherit;
}
.vdp-datepicker__calendar .cell.day-header {
font-size: 75%;
white-space: nowrap;
cursor: inherit;
}
.vdp-datepicker__calendar .cell.day-header:hover {
background: inherit;
}
.vdp-datepicker__calendar .month,
.vdp-datepicker__calendar .year {
width: 33.333%;
}
.vdp-datepicker__clear-button,
.vdp-datepicker__calendar-button {
cursor: pointer;
font-style: normal;
}
.vdp-datepicker__clear-button.disabled,
.vdp-datepicker__calendar-button.disabled {
color: #999;
cursor: default;
}
</style>

View File

@ -1,158 +0,0 @@
<template>
<div :class="{'input-group' : bootstrapStyling}">
<!-- Calendar Button -->
<span v-if="calendarButton" class="vdp-datepicker__calendar-button" :class="{'input-group-prepend' : bootstrapStyling}" @click="showCalendar" v-bind:style="{'cursor:not-allowed;' : disabled}">
<span :class="{'input-group-text' : bootstrapStyling}">
<font-awesome-icon :icon="calendarButtonIcon"/>
</span>
</span>
<!-- Input -->
<input
:type="inline ? 'hidden' : 'text'"
:class="[computedInputClass, {'invalid': isFieldValid}]"
:name="name"
:ref="refName"
:id="id"
:value="formattedValue"
:open-date="openDate"
:placeholder="placeholder"
:clear-button="clearButton"
:disabled="disabled"
:required="required"
:readonly="!typeable"
class="date-field"
@click="showCalendar"
@keyup="parseTypedDate"
@blur="inputBlurred"
autocomplete="off">
<!-- Clear Button -->
<span v-if="clearButton && selectedDate" class="vdp-datepicker__clear-button" :class="{'input-group-append' : bootstrapStyling}" @click="clearDate()">
<span :class="{'input-group-text' : bootstrapStyling}">
<!-- <i :class="clearButtonIcon">
<span v-if="!clearButtonIcon">&times;</span>
</i> -->
<font-awesome-icon :icon="clearButtonIcon"/>
</span>
</span>
<slot name="afterDateInput"></slot>
</div>
</template>
<script>
import { makeDateUtils } from './src/DateUtils'
export default {
props: {
selectedDate: Date,
resetTypedDate: [Date],
format: [String, Function],
translation: Object,
inline: Boolean,
id: String,
name: String,
refName: String,
openDate: Date,
placeholder: String,
inputClass: [String, Object, Array],
clearButton: Boolean,
clearButtonIcon: String,
calendarButton: Boolean,
calendarButtonIcon: String,
calendarButtonIconContent: String,
disabled: Boolean,
required: Boolean,
typeable: Boolean,
bootstrapStyling: Boolean,
useUtc: Boolean,
invalid: Boolean
},
data () {
const constructedDateUtils = makeDateUtils(this.useUtc)
return {
input: null,
typedDate: false,
utils: constructedDateUtils
}
},
computed: {
formattedValue () {
if (!this.selectedDate) {
return null
}
if (this.typedDate) {
return this.typedDate
}
return typeof this.format === 'function'
? this.format(this.selectedDate)
: this.utils.formatDate(new Date(this.selectedDate), this.format, this.translation)
},
computedInputClass () {
if (this.bootstrapStyling) {
if (typeof this.inputClass === 'string') {
return [this.inputClass, 'form-control'].join(' ')
}
return {'form-control': true, ...this.inputClass}
}
return this.inputClass
},
isFieldValid () {
return this.invalid
}
},
watch: {
resetTypedDate () {
this.typedDate = false
}
},
methods: {
showCalendar () {
this.$emit('showCalendar')
},
/**
* Attempt to parse a typed date
* @param {Event} event
*/
parseTypedDate (event) {
// close calendar if escape or enter are pressed
if ([
27, // escape
13 // enter
].includes(event.keyCode)) {
this.input.blur()
}
if (this.typeable) {
const typedDate = Date.parse(this.input.value)
if (!isNaN(typedDate)) {
this.typedDate = this.input.value
this.$emit('typedDate', new Date(this.typedDate))
}
}
},
/**
* nullify the typed date to defer to regular formatting
* called once the input is blurred
*/
inputBlurred () {
if (this.typeable && isNaN(Date.parse(this.input.value))) {
this.clearDate()
this.input.value = null
this.typedDate = null
}
this.$emit('closeCalendar')
},
/**
* emit a clearDate event
*/
clearDate () {
this.$emit('clearDate')
}
},
mounted () {
this.input = this.$el.querySelector('input')
}
}
// eslint-disable-next-line
;
</script>

View File

@ -1,375 +0,0 @@
<template>
<div :class="[calendarClass, 'vdp-datepicker__calendar']" v-show="showDayView" :style="calendarStyle" @mousedown.prevent>
<slot name="beforeCalendarHeader"></slot>
<header>
<span
@click="isRtl ? nextMonth() : previousMonth()"
class="prev"
:class="{'disabled': isLeftNavDisabled}">&lt;</span>
<span class="day__month_btn" @click="showMonthCalendar" :class="allowedToShowView('month') ? 'up' : ''">{{ isYmd ? currYearName : currMonthName }} {{ isYmd ? currMonthName : currYearName }}</span>
<span
@click="isRtl ? previousMonth() : nextMonth()"
class="next"
:class="{'disabled': isRightNavDisabled}">&gt;</span>
</header>
<div :class="isRtl ? 'flex-rtl' : ''">
<span class="cell day-header" v-for="d in daysOfWeek" :key="d.timestamp">{{ d }}</span>
<template v-if="blankDays > 0">
<span class="cell day blank" v-for="d in blankDays" :key="d.timestamp"></span>
</template><!--
--><span class="cell day"
v-for="day in days"
:key="day.timestamp"
:class="dayClasses(day)"
v-html="dayCellContent(day)"
@click="selectDate(day)"></span>
</div>
</div>
</template>
<script>
import { makeDateUtils } from './src/DateUtils'
export default {
props: {
showDayView: Boolean,
selectedDate: Date,
pageDate: Date,
pageTimestamp: Number,
fullMonthName: Boolean,
allowedToShowView: Function,
dayCellContent: {
type: Function,
default: day => day.date
},
disabledDates: Object,
highlighted: Object,
calendarClass: [String, Object, Array],
calendarStyle: Object,
translation: Object,
isRtl: Boolean,
mondayFirst: Boolean,
useUtc: Boolean
},
data () {
const constructedDateUtils = makeDateUtils(this.useUtc)
return {
utils: constructedDateUtils
}
},
computed: {
/**
* Returns an array of day names
* @return {String[]}
*/
daysOfWeek () {
if (this.mondayFirst) {
const tempDays = this.translation.days.slice()
tempDays.push(tempDays.shift())
return tempDays
}
return this.translation.days
},
/**
* Returns the day number of the week less one for the first of the current month
* Used to show amount of empty cells before the first in the day calendar layout
* @return {Number}
*/
blankDays () {
const d = this.pageDate
let dObj = this.useUtc
? new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1))
: new Date(d.getFullYear(), d.getMonth(), 1, d.getHours(), d.getMinutes())
if (this.mondayFirst) {
return this.utils.getDay(dObj) > 0 ? this.utils.getDay(dObj) - 1 : 6
}
return this.utils.getDay(dObj)
},
/**
* @return {Object[]}
*/
days () {
const d = this.pageDate
let days = []
// set up a new date object to the beginning of the current 'page'
let dObj = this.useUtc
? new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1))
: new Date(d.getFullYear(), d.getMonth(), 1, d.getHours(), d.getMinutes())
let daysInMonth = this.utils.daysInMonth(this.utils.getFullYear(dObj), this.utils.getMonth(dObj))
for (let i = 0; i < daysInMonth; i++) {
days.push({
date: this.utils.getDate(dObj),
timestamp: dObj.getTime(),
isSelected: this.isSelectedDate(dObj),
isDisabled: this.isDisabledDate(dObj),
isHighlighted: this.isHighlightedDate(dObj),
isHighlightStart: this.isHighlightStart(dObj),
isHighlightEnd: this.isHighlightEnd(dObj),
isToday: this.utils.compareDates(dObj, new Date()),
isWeekend: this.utils.getDay(dObj) === 0 || this.utils.getDay(dObj) === 6,
isSaturday: this.utils.getDay(dObj) === 6,
isSunday: this.utils.getDay(dObj) === 0
})
this.utils.setDate(dObj, this.utils.getDate(dObj) + 1)
}
return days
},
/**
* Gets the name of the month the current page is on
* @return {String}
*/
currMonthName () {
const monthName = this.fullMonthName ? this.translation.months : this.translation.monthsAbbr
return this.utils.getMonthNameAbbr(this.utils.getMonth(this.pageDate), monthName)
},
/**
* Gets the name of the year that current page is on
* @return {Number}
*/
currYearName () {
const yearSuffix = this.translation.yearSuffix
return `${this.utils.getFullYear(this.pageDate)}${yearSuffix}`
},
/**
* Is this translation using year/month/day format?
* @return {Boolean}
*/
isYmd () {
return this.translation.ymd && this.translation.ymd === true
},
/**
* Is the left hand navigation button disabled?
* @return {Boolean}
*/
isLeftNavDisabled () {
return this.isRtl
? this.isNextMonthDisabled(this.pageTimestamp)
: this.isPreviousMonthDisabled(this.pageTimestamp)
},
/**
* Is the right hand navigation button disabled?
* @return {Boolean}
*/
isRightNavDisabled () {
return this.isRtl
? this.isPreviousMonthDisabled(this.pageTimestamp)
: this.isNextMonthDisabled(this.pageTimestamp)
}
},
methods: {
selectDate (date) {
if (date.isDisabled) {
this.$emit('selectedDisabled', date)
return false
}
this.$emit('selectDate', date)
},
/**
* @return {Number}
*/
getPageMonth () {
return this.utils.getMonth(this.pageDate)
},
/**
* Emit an event to show the month picker
*/
showMonthCalendar () {
this.$emit('showMonthCalendar')
},
/**
* Change the page month
* @param {Number} incrementBy
*/
changeMonth (incrementBy) {
let date = this.pageDate
this.utils.setMonth(date, this.utils.getMonth(date) + incrementBy)
this.$emit('changedMonth', date)
},
/**
* Decrement the page month
*/
previousMonth () {
if (!this.isPreviousMonthDisabled()) {
this.changeMonth(-1)
}
},
/**
* Is the previous month disabled?
* @return {Boolean}
*/
isPreviousMonthDisabled () {
if (!this.disabledDates || !this.disabledDates.to) {
return false
}
let d = this.pageDate
return this.utils.getMonth(this.disabledDates.to) >= this.utils.getMonth(d) &&
this.utils.getFullYear(this.disabledDates.to) >= this.utils.getFullYear(d)
},
/**
* Increment the current page month
*/
nextMonth () {
if (!this.isNextMonthDisabled()) {
this.changeMonth(+1)
}
},
/**
* Is the next month disabled?
* @return {Boolean}
*/
isNextMonthDisabled () {
if (!this.disabledDates || !this.disabledDates.from) {
return false
}
let d = this.pageDate
return this.utils.getMonth(this.disabledDates.from) <= this.utils.getMonth(d) &&
this.utils.getFullYear(this.disabledDates.from) <= this.utils.getFullYear(d)
},
/**
* Whether a day is selected
* @param {Date}
* @return {Boolean}
*/
isSelectedDate (dObj) {
return this.selectedDate && this.utils.compareDates(this.selectedDate, dObj)
},
/**
* Whether a day is disabled
* @param {Date}
* @return {Boolean}
*/
isDisabledDate (date) {
let disabledDates = false
if (typeof this.disabledDates === 'undefined') {
return false
}
if (typeof this.disabledDates.dates !== 'undefined') {
this.disabledDates.dates.forEach((d) => {
if (this.utils.compareDates(date, d)) {
disabledDates = true
return true
}
})
}
if (typeof this.disabledDates.to !== 'undefined' && this.disabledDates.to && date < this.disabledDates.to) {
disabledDates = true
}
if (typeof this.disabledDates.from !== 'undefined' && this.disabledDates.from && date > this.disabledDates.from) {
disabledDates = true
}
if (typeof this.disabledDates.ranges !== 'undefined') {
this.disabledDates.ranges.forEach((range) => {
if (typeof range.from !== 'undefined' && range.from && typeof range.to !== 'undefined' && range.to) {
if (date < range.to && date > range.from) {
disabledDates = true
return true
}
}
})
}
if (typeof this.disabledDates.days !== 'undefined' && this.disabledDates.days.indexOf(this.utils.getDay(date)) !== -1) {
disabledDates = true
}
if (typeof this.disabledDates.daysOfMonth !== 'undefined' && this.disabledDates.daysOfMonth.indexOf(this.utils.getDate(date)) !== -1) {
disabledDates = true
}
if (typeof this.disabledDates.customPredictor === 'function' && this.disabledDates.customPredictor(date)) {
disabledDates = true
}
return disabledDates
},
/**
* Whether a day is highlighted (only if it is not disabled already except when highlighted.includeDisabled is true)
* @param {Date}
* @return {Boolean}
*/
isHighlightedDate (date) {
if (!(this.highlighted && this.highlighted.includeDisabled) && this.isDisabledDate(date)) {
return false
}
let highlighted = false
if (typeof this.highlighted === 'undefined') {
return false
}
if (typeof this.highlighted.dates !== 'undefined') {
this.highlighted.dates.forEach((d) => {
if (this.utils.compareDates(date, d)) {
highlighted = true
return true
}
})
}
if (this.isDefined(this.highlighted.from) && this.isDefined(this.highlighted.to)) {
highlighted = date >= this.highlighted.from && date <= this.highlighted.to
}
if (typeof this.highlighted.days !== 'undefined' && this.highlighted.days.indexOf(this.utils.getDay(date)) !== -1) {
highlighted = true
}
if (typeof this.highlighted.daysOfMonth !== 'undefined' && this.highlighted.daysOfMonth.indexOf(this.utils.getDate(date)) !== -1) {
highlighted = true
}
if (typeof this.highlighted.customPredictor === 'function' && this.highlighted.customPredictor(date)) {
highlighted = true
}
return highlighted
},
dayClasses (day) {
return {
'selected': day.isSelected,
'disabled': day.isDisabled,
'highlighted': day.isHighlighted,
'today': day.isToday,
'weekend': day.isWeekend,
'sat': day.isSaturday,
'sun': day.isSunday,
'highlight-start': day.isHighlightStart,
'highlight-end': day.isHighlightEnd
}
},
/**
* Whether a day is highlighted and it is the first date
* in the highlighted range of dates
* @param {Date}
* @return {Boolean}
*/
isHighlightStart (date) {
return this.isHighlightedDate(date) &&
(this.highlighted.from instanceof Date) &&
(this.utils.getFullYear(this.highlighted.from) === this.utils.getFullYear(date)) &&
(this.utils.getMonth(this.highlighted.from) === this.utils.getMonth(date)) &&
(this.utils.getDate(this.highlighted.from) === this.utils.getDate(date))
},
/**
* Whether a day is highlighted and it is the first date
* in the highlighted range of dates
* @param {Date}
* @return {Boolean}
*/
isHighlightEnd (date) {
return this.isHighlightedDate(date) &&
(this.highlighted.to instanceof Date) &&
(this.utils.getFullYear(this.highlighted.to) === this.utils.getFullYear(date)) &&
(this.utils.getMonth(this.highlighted.to) === this.utils.getMonth(date)) &&
(this.utils.getDate(this.highlighted.to) === this.utils.getDate(date))
},
/**
* Helper
* @param {mixed} prop
* @return {Boolean}
*/
isDefined (prop) {
return typeof prop !== 'undefined' && prop
}
}
}
// eslint-disable-next-line
;
</script>

View File

@ -1,200 +0,0 @@
<template>
<div :class="[calendarClass, 'vdp-datepicker__calendar']" v-show="showMonthView" :style="calendarStyle" @mousedown.prevent>
<slot name="beforeCalendarHeader"></slot>
<header>
<span
@click="isRtl ? nextYear() : previousYear()"
class="prev"
:class="{'disabled': isLeftNavDisabled}">&lt;</span>
<span class="month__year_btn" @click="showYearCalendar" :class="allowedToShowView('year') ? 'up' : ''">{{ pageYearName }}</span>
<span
@click="isRtl ? previousYear() : nextYear()"
class="next"
:class="{'disabled': isRightNavDisabled}">&gt;</span>
</header>
<span class="cell month"
v-for="month in months"
:key="month.timestamp"
:class="{'selected': month.isSelected, 'disabled': month.isDisabled}"
@click.stop="selectMonth(month)">{{ month.month }}</span>
</div>
</template>
<script>
import { makeDateUtils } from './src/DateUtils'
export default {
props: {
showMonthView: Boolean,
selectedDate: Date,
pageDate: Date,
pageTimestamp: Number,
disabledDates: Object,
calendarClass: [String, Object, Array],
calendarStyle: Object,
translation: Object,
isRtl: Boolean,
allowedToShowView: Function,
useUtc: Boolean
},
data () {
const constructedDateUtils = makeDateUtils(this.useUtc)
return {
utils: constructedDateUtils
}
},
computed: {
months () {
const d = this.pageDate
let months = []
// set up a new date object to the beginning of the current 'page'
let dObj = this.useUtc
? new Date(Date.UTC(d.getUTCFullYear(), 0, d.getUTCDate()))
: new Date(d.getFullYear(), 0, d.getDate(), d.getHours(), d.getMinutes())
for (let i = 0; i < 12; i++) {
months.push({
month: this.utils.getMonthName(i, this.translation.months),
timestamp: dObj.getTime(),
isSelected: this.isSelectedMonth(dObj),
isDisabled: this.isDisabledMonth(dObj)
})
this.utils.setMonth(dObj, this.utils.getMonth(dObj) + 1)
}
return months
},
/**
* Get year name on current page.
* @return {String}
*/
pageYearName () {
const yearSuffix = this.translation.yearSuffix
return `${this.utils.getFullYear(this.pageDate)}${yearSuffix}`
},
/**
* Is the left hand navigation disabled
* @return {Boolean}
*/
isLeftNavDisabled () {
return this.isRtl
? this.isNextYearDisabled(this.pageTimestamp)
: this.isPreviousYearDisabled(this.pageTimestamp)
},
/**
* Is the right hand navigation disabled
* @return {Boolean}
*/
isRightNavDisabled () {
return this.isRtl
? this.isPreviousYearDisabled(this.pageTimestamp)
: this.isNextYearDisabled(this.pageTimestamp)
}
},
methods: {
/**
* Emits a selectMonth event
* @param {Object} month
*/
selectMonth (month) {
if (month.isDisabled) {
return false
}
this.$emit('selectMonth', month)
},
/**
* Changes the year up or down
* @param {Number} incrementBy
*/
changeYear (incrementBy) {
let date = this.pageDate
this.utils.setFullYear(date, this.utils.getFullYear(date) + incrementBy)
this.$emit('changedYear', date)
},
/**
* Decrements the year
*/
previousYear () {
if (!this.isPreviousYearDisabled()) {
this.changeYear(-1)
}
},
/**
* Checks if the previous year is disabled or not
* @return {Boolean}
*/
isPreviousYearDisabled () {
if (!this.disabledDates || !this.disabledDates.to) {
return false
}
return this.utils.getFullYear(this.disabledDates.to) >= this.utils.getFullYear(this.pageDate)
},
/**
* Increments the year
*/
nextYear () {
if (!this.isNextYearDisabled()) {
this.changeYear(1)
}
},
/**
* Checks if the next year is disabled or not
* @return {Boolean}
*/
isNextYearDisabled () {
if (!this.disabledDates || !this.disabledDates.from) {
return false
}
return this.utils.getFullYear(this.disabledDates.from) <= this.utils.getFullYear(this.pageDate)
},
/**
* Emits an event that shows the year calendar
*/
showYearCalendar () {
this.$emit('showYearCalendar')
},
/**
* Whether the selected date is in this month
* @param {Date}
* @return {Boolean}
*/
isSelectedMonth (date) {
return (this.selectedDate &&
this.utils.getFullYear(this.selectedDate) === this.utils.getFullYear(date) &&
this.utils.getMonth(this.selectedDate) === this.utils.getMonth(date))
},
/**
* Whether a month is disabled
* @param {Date}
* @return {Boolean}
*/
isDisabledMonth (date) {
let disabledDates = false
if (typeof this.disabledDates === 'undefined') {
return false
}
if (typeof this.disabledDates.to !== 'undefined' && this.disabledDates.to) {
if (
(this.utils.getMonth(date) < this.utils.getMonth(this.disabledDates.to) && this.utils.getFullYear(date) <= this.utils.getFullYear(this.disabledDates.to)) ||
this.utils.getFullYear(date) < this.utils.getFullYear(this.disabledDates.to)
) {
disabledDates = true
}
}
if (typeof this.disabledDates.from !== 'undefined' && this.disabledDates.from) {
if (
(this.utils.getMonth(date) > this.utils.getMonth(this.disabledDates.from) && this.utils.getFullYear(date) >= this.utils.getFullYear(this.disabledDates.from)) ||
this.utils.getFullYear(date) > this.utils.getFullYear(this.disabledDates.from)
) {
disabledDates = true
}
}
if (typeof this.disabledDates.customPredictor === 'function' && this.disabledDates.customPredictor(date)) {
disabledDates = true
}
return disabledDates
}
}
}
// eslint-disable-next-line
;
</script>

View File

@ -1,174 +0,0 @@
<template>
<div :class="[calendarClass, 'vdp-datepicker__calendar']" v-show="showYearView" :style="calendarStyle" @mousedown.prevent>
<slot name="beforeCalendarHeader"></slot>
<header>
<span
@click="isRtl ? nextDecade() : previousDecade()"
class="prev"
:class="{'disabled': isLeftNavDisabled}">&lt;</span>
<span>{{ getPageDecade }}</span>
<span
@click="isRtl ? previousDecade() : nextDecade()"
class="next"
:class="{'disabled': isRightNavDisabled}">&gt;</span>
</header>
<span
class="cell year"
v-for="year in years"
:key="year.timestamp"
:class="{ 'selected': year.isSelected, 'disabled': year.isDisabled }"
@click.stop="selectYear(year)">{{ year.year }}</span>
</div>
</template>
<script>
import { makeDateUtils } from './src/DateUtils'
export default {
props: {
showYearView: Boolean,
selectedDate: Date,
pageDate: Date,
pageTimestamp: Number,
disabledDates: Object,
highlighted: Object,
calendarClass: [String, Object, Array],
calendarStyle: Object,
translation: Object,
isRtl: Boolean,
allowedToShowView: Function,
useUtc: Boolean
},
computed: {
years () {
const d = this.pageDate
let years = []
// set up a new date object to the beginning of the current 'page'7
let dObj = this.useUtc
? new Date(Date.UTC(Math.floor(d.getUTCFullYear() / 10) * 10, d.getUTCMonth(), d.getUTCDate()))
: new Date(Math.floor(d.getFullYear() / 10) * 10, d.getMonth(), d.getDate(), d.getHours(), d.getMinutes())
for (let i = 0; i < 10; i++) {
years.push({
year: this.utils.getFullYear(dObj),
timestamp: dObj.getTime(),
isSelected: this.isSelectedYear(dObj),
isDisabled: this.isDisabledYear(dObj)
})
this.utils.setFullYear(dObj, this.utils.getFullYear(dObj) + 1)
}
return years
},
/**
* @return {String}
*/
getPageDecade () {
const decadeStart = Math.floor(this.utils.getFullYear(this.pageDate) / 10) * 10
const decadeEnd = decadeStart + 9
const yearSuffix = this.translation.yearSuffix
return `${decadeStart} - ${decadeEnd}${yearSuffix}`
},
/**
* Is the left hand navigation button disabled?
* @return {Boolean}
*/
isLeftNavDisabled () {
return this.isRtl
? this.isNextDecadeDisabled(this.pageTimestamp)
: this.isPreviousDecadeDisabled(this.pageTimestamp)
},
/**
* Is the right hand navigation button disabled?
* @return {Boolean}
*/
isRightNavDisabled () {
return this.isRtl
? this.isPreviousDecadeDisabled(this.pageTimestamp)
: this.isNextDecadeDisabled(this.pageTimestamp)
}
},
data () {
const constructedDateUtils = makeDateUtils(this.useUtc)
return {
utils: constructedDateUtils
}
},
methods: {
selectYear (year) {
if (year.isDisabled) {
return false
}
this.$emit('selectYear', year)
},
changeYear (incrementBy) {
let date = this.pageDate
this.utils.setFullYear(date, this.utils.getFullYear(date) + incrementBy)
this.$emit('changedDecade', date)
},
previousDecade () {
if (this.isPreviousDecadeDisabled()) {
return false
}
this.changeYear(-10)
},
isPreviousDecadeDisabled () {
if (!this.disabledDates || !this.disabledDates.to) {
return false
}
const disabledYear = this.utils.getFullYear(this.disabledDates.to)
const lastYearInPreviousPage = Math.floor(this.utils.getFullYear(this.pageDate) / 10) * 10 - 1
return disabledYear > lastYearInPreviousPage
},
nextDecade () {
if (this.isNextDecadeDisabled()) {
return false
}
this.changeYear(10)
},
isNextDecadeDisabled () {
if (!this.disabledDates || !this.disabledDates.from) {
return false
}
const disabledYear = this.utils.getFullYear(this.disabledDates.from)
const firstYearInNextPage = Math.ceil(this.utils.getFullYear(this.pageDate) / 10) * 10
return disabledYear < firstYearInNextPage
},
/**
* Whether the selected date is in this year
* @param {Date}
* @return {Boolean}
*/
isSelectedYear (date) {
return this.selectedDate && this.utils.getFullYear(this.selectedDate) === this.utils.getFullYear(date)
},
/**
* Whether a year is disabled
* @param {Date}
* @return {Boolean}
*/
isDisabledYear (date) {
let disabledDates = false
if (typeof this.disabledDates === 'undefined' || !this.disabledDates) {
return false
}
if (typeof this.disabledDates.to !== 'undefined' && this.disabledDates.to) {
if (this.utils.getFullYear(date) < this.utils.getFullYear(this.disabledDates.to)) {
disabledDates = true
}
}
if (typeof this.disabledDates.from !== 'undefined' && this.disabledDates.from) {
if (this.utils.getFullYear(date) > this.utils.getFullYear(this.disabledDates.from)) {
disabledDates = true
}
}
if (typeof this.disabledDates.customPredictor === 'function' && this.disabledDates.customPredictor(date)) {
disabledDates = true
}
return disabledDates
}
}
}
// eslint-disable-next-line
;
</script>

View File

@ -1,252 +0,0 @@
import en from './locale/translations/en'
const utils = {
/**
* @type {Boolean}
*/
useUtc: false,
/**
* Returns the full year, using UTC or not
* @param {Date} date
*/
getFullYear (date) {
return this.useUtc ? date.getUTCFullYear() : date.getFullYear()
},
/**
* Returns the month, using UTC or not
* @param {Date} date
*/
getMonth (date) {
return this.useUtc ? date.getUTCMonth() : date.getMonth()
},
/**
* Returns the date, using UTC or not
* @param {Date} date
*/
getDate (date) {
return this.useUtc ? date.getUTCDate() : date.getDate()
},
/**
* Returns the day, using UTC or not
* @param {Date} date
*/
getDay (date) {
return this.useUtc ? date.getUTCDay() : date.getDay()
},
/**
* Returns the hours, using UTC or not
* @param {Date} date
*/
getHours (date) {
return this.useUtc ? date.getUTCHours() : date.getHours()
},
/**
* Returns the minutes, using UTC or not
* @param {Date} date
*/
getMinutes (date) {
return this.useUtc ? date.getUTCMinutes() : date.getMinutes()
},
/**
* Sets the full year, using UTC or not
* @param {Date} date
*/
setFullYear (date, value, useUtc) {
return this.useUtc ? date.setUTCFullYear(value) : date.setFullYear(value)
},
/**
* Sets the month, using UTC or not
* @param {Date} date
*/
setMonth (date, value, useUtc) {
return this.useUtc ? date.setUTCMonth(value) : date.setMonth(value)
},
/**
* Sets the date, using UTC or not
* @param {Date} date
* @param {Number} value
*/
setDate (date, value, useUtc) {
return this.useUtc ? date.setUTCDate(value) : date.setDate(value)
},
/**
* Check if date1 is equivalent to date2, without comparing the time
* @see https://stackoverflow.com/a/6202196/4455925
* @param {Date} date1
* @param {Date} date2
*/
compareDates (date1, date2) {
const d1 = new Date(date1.getTime())
const d2 = new Date(date2.getTime())
if (this.useUtc) {
d1.setUTCHours(0, 0, 0, 0)
d2.setUTCHours(0, 0, 0, 0)
} else {
d1.setHours(0, 0, 0, 0)
d2.setHours(0, 0, 0, 0)
}
return d1.getTime() === d2.getTime()
},
/**
* Validates a date object
* @param {Date} date - an object instantiated with the new Date constructor
* @return {Boolean}
*/
isValidDate (date) {
if (Object.prototype.toString.call(date) !== '[object Date]') {
return false
}
return !isNaN(date.getTime())
},
/**
* Return abbreviated week day name
* @param {Date}
* @param {Array}
* @return {String}
*/
getDayNameAbbr (date, days) {
if (typeof date !== 'object') {
throw TypeError('Invalid Type')
}
return days[this.getDay(date)]
},
/**
* Return name of the month
* @param {Number|Date}
* @param {Array}
* @return {String}
*/
getMonthName (month, months) {
if (!months) {
throw Error('missing 2nd parameter Months array')
}
if (typeof month === 'object') {
return months[this.getMonth(month)]
}
if (typeof month === 'number') {
return months[month]
}
throw TypeError('Invalid type')
},
/**
* Return an abbreviated version of the month
* @param {Number|Date}
* @return {String}
*/
getMonthNameAbbr (month, monthsAbbr) {
if (!monthsAbbr) {
throw Error('missing 2nd paramter Months array')
}
if (typeof month === 'object') {
return monthsAbbr[this.getMonth(month)]
}
if (typeof month === 'number') {
return monthsAbbr[month]
}
throw TypeError('Invalid type')
},
/**
* Alternative get total number of days in month
* @param {Number} year
* @param {Number} m
* @return {Number}
*/
daysInMonth (year, month) {
return /8|3|5|10/.test(month) ? 30 : month === 1 ? (!(year % 4) && year % 100) || !(year % 400) ? 29 : 28 : 31
},
/**
* Get nth suffix for date
* @param {Number} day
* @return {String}
*/
getNthSuffix (day) {
switch (day) {
case 1:
case 21:
case 31:
return 'st'
case 2:
case 22:
return 'nd'
case 3:
case 23:
return 'rd'
default:
return 'th'
}
},
/**
* Formats date object
* @param {Date}
* @param {String}
* @param {Object}
* @return {String}
*/
formatDate (date, format, translation) {
translation = (!translation) ? en : translation
let year = this.getFullYear(date)
let month = this.getMonth(date) + 1
let day = this.getDate(date)
let str = format
.replace(/dd/, ('0' + day).slice(-2))
.replace(/d/, day)
.replace(/yyyy/, year)
.replace(/yy/, String(year).slice(2))
.replace(/MMMM/, this.getMonthName(this.getMonth(date), translation.months))
.replace(/MMM/, this.getMonthNameAbbr(this.getMonth(date), translation.monthsAbbr))
.replace(/MM/, ('0' + month).slice(-2))
.replace(/M(?!a|ä|e)/, month)
.replace(/su/, this.getNthSuffix(this.getDate(date)))
.replace(/D(?!e|é|i)/, this.getDayNameAbbr(date, translation.days))
return str
},
/**
* Creates an array of dates for each day in between two dates.
* @param {Date} start
* @param {Date} end
* @return {Array}
*/
createDateArray (start, end) {
let dates = []
while (start <= end) {
dates.push(new Date(start))
start = this.setDate(new Date(start), this.getDate(new Date(start)) + 1)
}
return dates
},
/**
* method used as a prop validator for input values
* @param {*} val
* @return {Boolean}
*/
validateDateInput (val) {
return val === null || val instanceof Date || typeof val === 'string' || typeof val === 'number'
}
}
export const makeDateUtils = useUtc => ({...utils, useUtc})
export default {
...utils
}
// eslint-disable-next-line
;

View File

@ -1,57 +0,0 @@
export default class Language {
constructor (language, months, monthsAbbr, days) {
this.language = language
this.months = months
this.monthsAbbr = monthsAbbr
this.days = days
this.rtl = false
this.ymd = false
this.yearSuffix = ''
}
get language () {
return this._language
}
set language (language) {
if (typeof language !== 'string') {
throw new TypeError('Language must be a string')
}
this._language = language
}
get months () {
return this._months
}
set months (months) {
if (months.length !== 12) {
throw new RangeError(`There must be 12 months for ${this.language} language`)
}
this._months = months
}
get monthsAbbr () {
return this._monthsAbbr
}
set monthsAbbr (monthsAbbr) {
if (monthsAbbr.length !== 12) {
throw new RangeError(`There must be 12 abbreviated months for ${this.language} language`)
}
this._monthsAbbr = monthsAbbr
}
get days () {
return this._days
}
set days (days) {
if (days.length !== 7) {
throw new RangeError(`There must be 7 days for ${this.language} language`)
}
this._days = days
}
}
// eslint-disable-next-line
;

View File

@ -1,105 +0,0 @@
import af from './translations/af'
import ar from './translations/ar'
import bg from './translations/bg'
import bs from './translations/bs'
import ca from './translations/ca'
import cs from './translations/cs'
import da from './translations/da'
import de from './translations/de'
import ee from './translations/ee'
import el from './translations/el'
import en from './translations/en'
import es from './translations/es'
import fa from './translations/fa'
import fi from './translations/fi'
import fo from './translations/fo'
import fr from './translations/fr'
import ge from './translations/ge'
import gl from './translations/gl'
import he from './translations/he'
import hr from './translations/hr'
import hu from './translations/hu'
import id from './translations/id'
import is from './translations/is'
import it from './translations/it'
import ja from './translations/ja'
import kk from './translations/kk'
import ko from './translations/ko'
import lb from './translations/lb'
import lt from './translations/lt'
import lv from './translations/lv'
import mk from './translations/mk'
import mn from './translations/mn'
import nbNO from './translations/nb-NO'
import nl from './translations/nl'
import pl from './translations/pl'
import ptBR from './translations/pt-BR'
import ro from './translations/ro'
import ru from './translations/ru'
import sk from './translations/sk'
import slSI from './translations/sl-SI'
import srCYRL from './translations/sr-CYRL'
import sr from './translations/sr'
import sv from './translations/sv'
import th from './translations/th'
import tr from './translations/tr'
import uk from './translations/uk'
import ur from './translations/ur'
import vi from './translations/vi'
import zh from './translations/zh'
import zhHK from './translations/zh-HK'
export {
af,
ar,
bg,
bs,
ca,
cs,
da,
de,
ee,
el,
en,
es,
fa,
fi,
fo,
fr,
ge,
gl,
he,
hr,
hu,
id,
is,
it,
ja,
kk,
ko,
lb,
lt,
lv,
mk,
mn,
nbNO,
nl,
pl,
ptBR,
ro,
ru,
sk,
slSI,
srCYRL,
sr,
sv,
th,
tr,
uk,
ur,
vi,
zh,
zhHK
}
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Afrikaans',
['Januarie', 'Februarie', 'Maart', 'April', 'Mei', 'Junie', 'Julie', 'Augustus', 'September', 'Oktober', 'November', 'Desember'],
['Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
['So.', 'Ma.', 'Di.', 'Wo.', 'Do.', 'Vr.', 'Sa.']
)
// eslint-disable-next-line
;

View File

@ -1,14 +0,0 @@
import Language from '../Language'
const language = new Language(
'Arabic',
['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوڤمبر', 'ديسمبر'],
['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوڤمبر', 'ديسمبر'],
['أحد', 'إثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت']
)
language.rtl = true
export default language
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Bulgarian',
['Януари', 'Февруари', 'Март', 'Април', 'Май', 'Юни', 'Юли', 'Август', 'Септември', 'Октомври', 'Ноември', 'Декември'],
['Ян', 'Фев', 'Мар', 'Апр', 'Май', 'Юни', 'Юли', 'Авг', 'Сеп', 'Окт', 'Ное', 'Дек'],
['Нд', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Bosnian',
['Januar', 'Februar', 'Mart', 'April', 'Maj', 'Juni', 'Juli', 'Avgust', 'Septembar', 'Oktobar', 'Novembar', 'Decembar'],
['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Avg', 'Sep', 'Okt', 'Nov', 'Dec'],
['Ned', 'Pon', 'Uto', 'Sri', 'Čet', 'Pet', 'Sub']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Catalan',
['Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juliol', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre'],
['Gen', 'Feb', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Oct', 'Nov', 'Des'],
['Diu', 'Dil', 'Dmr', 'Dmc', 'Dij', 'Div', 'Dis']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Czech',
['leden', 'únor', 'březen', 'duben', 'květen', 'červen', 'červenec', 'srpen', 'září', 'říjen', 'listopad', 'prosinec'],
['led', 'úno', 'bře', 'dub', 'kvě', 'čer', 'čec', 'srp', 'zář', 'říj', 'lis', 'pro'],
['ne', 'po', 'út', 'st', 'čt', 'pá', 'so']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Danish',
['Januar', 'Februar', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December'],
['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'],
['Sø', 'Ma', 'Ti', 'On', 'To', 'Fr', 'Lø']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'German',
['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
['So.', 'Mo.', 'Di.', 'Mi.', 'Do.', 'Fr.', 'Sa.']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Estonian',
['Jaanuar', 'Veebruar', 'Märts', 'Aprill', 'Mai', 'Juuni', 'Juuli', 'August', 'September', 'Oktoober', 'November', 'Detsember'],
['Jaan', 'Veebr', 'Märts', 'Apr', 'Mai', 'Juuni', 'Juuli', 'Aug', 'Sept', 'Okt', 'Nov', 'Dets'],
['P', 'E', 'T', 'K', 'N', 'R', 'L']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Greek',
['Ιανουάριος', 'Φεβρουάριος', 'Μάρτιος', 'Απρίλιος', 'Μάϊος', 'Ιούνιος', 'Ιούλιος', 'Αύγουστος', 'Σεπτέμβριος', 'Οκτώβριος', 'Νοέμβριος', 'Δεκέμβριος'],
['Ιαν', 'Φεβ', 'Μαρ', 'Απρ', 'Μαι', 'Ιουν', 'Ιουλ', 'Αυγ', 'Σεπ', 'Οκτ', 'Νοε', 'Δεκ'],
['Κυρ', 'Δευ', 'Τρι', 'Τετ', 'Πεμ', 'Παρ', 'Σαβ']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'English',
['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Spanish',
['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'],
['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Persian',
['فروردین', 'اردیبهشت', 'خرداد', 'تیر', 'مرداد', 'شهریور', 'مهر', 'آبان', 'آذر', 'دی', 'بهمن', 'اسفند'],
['فرو', 'ارد', 'خرد', 'تیر', 'مرد', 'شهر', 'مهر', 'آبا', 'آذر', 'دی', 'بهم', 'اسف'],
['یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه', 'شنبه']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Finnish',
['tammikuu', 'helmikuu', 'maaliskuu', 'huhtikuu', 'toukokuu', 'kesäkuu', 'heinäkuu', 'elokuu', 'syyskuu', 'lokakuu', 'marraskuu', 'joulukuu'],
['tammi', 'helmi', 'maalis', 'huhti', 'touko', 'kesä', 'heinä', 'elo', 'syys', 'loka', 'marras', 'joulu'],
['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Faroese',
['Januar', 'Februar', 'Mars', 'Apríl', 'Mai', 'Juni', 'Juli', 'August', 'Septembur', 'Oktobur', 'Novembur', 'Desembur'],
['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
['Sun', 'Mán', 'Týs', 'Mik', 'Hós', 'Frí', 'Ley']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'French',
['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Juin', 'Juil', 'Août', 'Sep', 'Oct', 'Nov', 'Déc'],
['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Georgia',
['იანვარი', 'თებერვალი', 'მარტი', 'აპრილი', 'მაისი', 'ივნისი', 'ივლისი', 'აგვისტო', 'სექტემბერი', 'ოქტომბერი', 'ნოემბერი', 'დეკემბერი'],
['იან', 'თებ', 'მარ', 'აპრ', 'მაი', 'ივნ', 'ივლ', 'აგვ', 'სექ', 'ოქტ', 'ნოე', 'დეკ'],
['კვი', 'ორშ', 'სამ', 'ოთხ', 'ხუთ', 'პარ', 'შაბ']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Galician',
['Xaneiro', 'Febreiro', 'Marzo', 'Abril', 'Maio', 'Xuño', 'Xullo', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Decembro'],
['Xan', 'Feb', 'Mar', 'Abr', 'Mai', 'Xuñ', 'Xul', 'Ago', 'Set', 'Out', 'Nov', 'Dec'],
['Dom', 'Lun', 'Mar', 'Mér', 'Xov', 'Ven', 'Sáb']
)
// eslint-disable-next-line
;

View File

@ -1,14 +0,0 @@
import Language from '../Language'
const language = new Language(
'Hebrew',
['ינואר', 'פברואר', 'מרץ', 'אפריל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ספטמבר', 'אוקטובר', 'נובמבר', 'דצמבר'],
['ינו', 'פבר', 'מרץ', 'אפר', 'מאי', 'יונ', 'יול', 'אוג', 'ספט', 'אוק', 'נוב', 'דצמ'],
['א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ש']
)
language.rtl = true
export default language
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Croatian',
['Siječanj', 'Veljača', 'Ožujak', 'Travanj', 'Svibanj', 'Lipanj', 'Srpanj', 'Kolovoz', 'Rujan', 'Listopad', 'Studeni', 'Prosinac'],
['Sij', 'Velj', 'Ožu', 'Tra', 'Svi', 'Lip', 'Srp', 'Kol', 'Ruj', 'Lis', 'Stu', 'Pro'],
['Ned', 'Pon', 'Uto', 'Sri', 'Čet', 'Pet', 'Sub']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Hungarian',
['Január', 'Február', 'Március', 'Április', 'Május', 'Június', 'Július', 'Augusztus', 'Szeptember', 'Október', 'November', 'December'],
['Jan', 'Febr', 'Márc', 'Ápr', 'Máj', 'Jún', 'Júl', 'Aug', 'Szept', 'Okt', 'Nov', 'Dec'],
['Vas', 'Hét', 'Ke', 'Sze', 'Csü', 'Pén', 'Szo']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Indonesian',
['Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', 'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'],
['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'],
['Min', 'Sen', 'Sel', 'Rab', 'Kam', 'Jum', 'Sab']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Icelandic',
['Janúar', 'Febrúar', 'Mars', 'Apríl', 'Maí', 'Júní', 'Júlí', 'Ágúst', 'September', 'Október', 'Nóvember', 'Desember'],
['Jan', 'Feb', 'Mars', 'Apr', 'Maí', 'Jún', 'Júl', 'Ágú', 'Sep', 'Okt', 'Nóv', 'Des'],
['Sun', 'Mán', 'Þri', 'Mið', 'Fim', 'Fös', 'Lau']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Italian',
['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'],
['Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic'],
['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab']
)
// eslint-disable-next-line
;

View File

@ -1,15 +0,0 @@
import Language from '../Language'
const language = new Language(
'Japanese',
['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
['日', '月', '火', '水', '木', '金', '土']
)
language.yearSuffix = '年'
language.ymd = true
export default language
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Kazakh',
['Қаңтар', 'Ақпан', 'Наурыз', 'Сәуір', 'Мамыр', 'Маусым', 'Шілде', 'Тамыз', 'Қыркүйек', 'Қазан', 'Қараша', 'Желтоқсан'],
['Қаң', 'Ақп', 'Нау', 'Сәу', 'Мам', 'Мау', 'Шіл', 'Там', 'Қыр', 'Қаз', 'Қар', 'Жел'],
['Жк', 'Дй', 'Сй', 'Ср', 'Бй', 'Жм', 'Сн']
)
// eslint-disable-next-line
;

View File

@ -1,14 +0,0 @@
import Language from '../Language'
const language = new Language(
'Korean',
['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
['일', '월', '화', '수', '목', '금', '토']
)
language.yearSuffix = '년'
language.ymd = true
export default language
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Luxembourgish',
['Januar', 'Februar', 'Mäerz', 'Abrëll', 'Mee', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
['Jan', 'Feb', 'Mäe', 'Abr', 'Mee', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
['So.', 'Mé.', 'Dë.', 'Më.', 'Do.', 'Fr.', 'Sa.']
)
// eslint-disable-next-line
;

View File

@ -1,14 +0,0 @@
import Language from '../Language'
const language = new Language(
'Lithuanian',
['Sausis', 'Vasaris', 'Kovas', 'Balandis', 'Gegužė', 'Birželis', 'Liepa', 'Rugpjūtis', 'Rugsėjis', 'Spalis', 'Lapkritis', 'Gruodis'],
['Sau', 'Vas', 'Kov', 'Bal', 'Geg', 'Bir', 'Lie', 'Rugp', 'Rugs', 'Spa', 'Lap', 'Gru'],
['Sek', 'Pir', 'Ant', 'Tre', 'Ket', 'Pen', 'Šeš']
)
language.ymd = true
export default language
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Latvian',
['Janvāris', 'Februāris', 'Marts', 'Aprīlis', 'Maijs', 'Jūnijs', 'Jūlijs', 'Augusts', 'Septembris', 'Oktobris', 'Novembris', 'Decembris'],
['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jūn', 'Jūl', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'],
['Sv', 'Pr', 'Ot', 'Tr', 'Ce', 'Pk', 'Se']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Macedonian',
['Јануари', 'Февруари', 'Март', 'Април', 'Мај', 'Јуни', 'Јули', 'Август', 'Септември', 'Октомври', 'Ноември', 'Декември'],
['Јан', 'Фев', 'Мар', 'Апр', 'Мај', 'Јун', 'Јул', 'Авг', 'Сеп', 'Окт', 'Ное', 'Дек'],
['Нед', 'Пон', 'Вто', 'Сре', 'Чет', 'Пет', 'Саб']
)
// eslint-disable-next-line
;

View File

@ -1,14 +0,0 @@
import Language from '../Language'
const language = new Language(
'Mongolia',
['1 дүгээр сар', '2 дугаар сар', '3 дугаар сар', '4 дүгээр сар', '5 дугаар сар', '6 дугаар сар', '7 дугаар сар', '8 дугаар сар', '9 дүгээр сар', '10 дугаар сар', '11 дүгээр сар', '12 дугаар сар'],
['1-р сар', '2-р сар', '3-р сар', '4-р сар', '5-р сар', '6-р сар', '7-р сар', '8-р сар', '9-р сар', '10-р сар', '11-р сар', '12-р сар'],
['Ня', 'Да', 'Мя', 'Лх', 'Пү', 'Ба', 'Бя']
)
language.ymd = true
export default language
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Norwegian Bokmål',
['Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Desember'],
['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
['Sø', 'Ma', 'Ti', 'On', 'To', 'Fr', 'Lø']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Dutch',
['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'],
['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'],
['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Polish',
['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
['Sty', 'Lut', 'Mar', 'Kwi', 'Maj', 'Cze', 'Lip', 'Sie', 'Wrz', 'Paź', 'Lis', 'Gru'],
['Nd', 'Pn', 'Wt', 'Śr', 'Czw', 'Pt', 'Sob']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Brazilian',
['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'],
['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sab']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Romanian',
['Ianuarie', 'Februarie', 'Martie', 'Aprilie', 'Mai', 'Iunie', 'Iulie', 'August', 'Septembrie', 'Octombrie', 'Noiembrie', 'Decembrie'],
['Ian', 'Feb', 'Mar', 'Apr', 'Mai', 'Iun', 'Iul', 'Aug', 'Sep', 'Oct', 'Noi', 'Dec'],
['D', 'L', 'Ma', 'Mi', 'J', 'V', 'S']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Russian',
['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'],
['Янв', 'Февр', 'Март', 'Апр', 'Май', 'Июнь', 'Июль', 'Авг', 'Сент', 'Окт', 'Нояб', 'Дек'],
['Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Slovakian',
['január', 'február', 'marec', 'apríl', 'máj', 'jún', 'júl', 'august', 'september', 'október', 'november', 'december'],
['jan', 'feb', 'mar', 'apr', 'máj', 'jún', 'júl', 'aug', 'sep', 'okt', 'nov', 'dec'],
['ne', 'po', 'ut', 'st', 'št', 'pi', 'so']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Sloveian',
['Januar', 'Februar', 'Marec', 'April', 'Maj', 'Junij', 'Julij', 'Avgust', 'September', 'Oktober', 'November', 'December'],
['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Avg', 'Sep', 'Okt', 'Nov', 'Dec'],
['Ned', 'Pon', 'Tor', 'Sre', 'Čet', 'Pet', 'Sob']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Serbian in Cyrillic script',
['Јануар', 'Фебруар', 'Март', 'Април', 'Мај', 'Јун', 'Јул', 'Август', 'Септембар', 'Октобар', 'Новембар', 'Децембар'],
['Јан', 'Феб', 'Мар', 'Апр', 'Мај', 'Јун', 'Јул', 'Авг', 'Сеп', 'Окт', 'Нов', 'Дец'],
['Нед', 'Пон', 'Уто', 'Сре', 'Чет', 'Пет', 'Суб']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Serbian',
['Januar', 'Februar', 'Mart', 'April', 'Maj', 'Jun', 'Jul', 'Avgust', 'Septembar', 'Oktobar', 'Novembar', 'Decembar'],
['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Avg', 'Sep', 'Okt', 'Nov', 'Dec'],
['Ned', 'Pon', 'Uto', 'Sre', 'Čet', 'Pet', 'Sub']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Swedish',
['Januari', 'Februari', 'Mars', 'April', 'Maj', 'Juni', 'Juli', 'Augusti', 'September', 'Oktober', 'November', 'December'],
['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'],
['Sön', 'Mån', 'Tis', 'Ons', 'Tor', 'Fre', 'Lör']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Thai',
['มกราคม', 'กุมภาพันธ์', 'มีนาคม', 'เมษายน', 'พฤษภาคม', 'มิถุนายน', 'กรกฎาคม', 'สิงหาคม', 'กันยายน', 'ตุลาคม', 'พฤศจิกายน', 'ธันวาคม'],
['ม.ค.', 'ก.พ.', 'มี.ค.', 'เม.ย.', 'พ.ค.', 'มิ.ย.', 'ก.ค.', 'ส.ค.', 'ก.ย.', 'ต.ค.', 'พ.ย.', 'ธ.ค.'],
['อา', 'จ', 'อ', 'พ', 'พฤ', 'ศ', 'ส']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Turkish',
['Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'Ağustos', 'Eylül', 'Ekim', 'Kasım', 'Aralık'],
['Oca', 'Şub', 'Mar', 'Nis', 'May', 'Haz', 'Tem', 'Ağu', 'Eyl', 'Eki', 'Kas', 'Ara'],
['Paz', 'Pzt', 'Sal', 'Çar', 'Per', 'Cum', 'Cmt']
)
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Ukraine',
['Січень', 'Лютий', 'Березень', 'Квітень', 'Травень', 'Червень', 'Липень', 'Серпень', 'Вересень', 'Жовтень', 'Листопад', 'Грудень'],
['Січ', 'Лют', 'Бер', 'Квіт', 'Трав', 'Чер', 'Лип', 'Серп', 'Вер', 'Жовт', 'Лист', 'Груд'],
['Нд', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб']
)
// eslint-disable-next-line
;

View File

@ -1,14 +0,0 @@
import Language from '../Language'
const language = new Language(
'Urdu',
['جنوری', 'فروری', 'مارچ', 'اپریل', 'مئی', 'جون', 'جولائی', 'اگست', 'سپتمبر', 'اکتوبر', 'نومبر', 'دسمبر'],
['جنوری', 'فروری', 'مارچ', 'اپریل', 'مئی', 'جون', 'جولائی', 'اگست', 'سپتمبر', 'اکتوبر', 'نومبر', 'دسمبر'],
['اتوار', 'پیر', 'منگل', 'بدھ', 'جمعرات', 'جمعہ', 'ہفتہ']
)
language.rtl = true
export default language
// eslint-disable-next-line
;

View File

@ -1,10 +0,0 @@
import Language from '../Language'
export default new Language(
'Vietnamese',
['Tháng 1', 'Tháng 2', 'Tháng 3', 'Tháng 4', 'Tháng 5', 'Tháng 6', 'Tháng 7', 'Tháng 8', 'Tháng 9', 'Tháng 10', 'Tháng 11', 'Tháng 12'],
['T 01', 'T 02', 'T 03', 'T 04', 'T 05', 'T 06', 'T 07', 'T 08', 'T 09', 'T 10', 'T 11', 'T 12'],
['CN', 'Thứ 2', 'Thứ 3', 'Thứ 4', 'Thứ 5', 'Thứ 6', 'Thứ 7']
)
// eslint-disable-next-line
;

View File

@ -1,11 +0,0 @@
import Language from '../Language'
const language = new Language(
'Chinese_HK',
['壹月', '贰月', '叁月', '肆月', '伍月', '陆月', '柒月', '捌月', '玖月', '拾月', '拾壹月', '拾贰月'],
['壹月', '贰月', '叁月', '肆月', '伍月', '陆月', '柒月', '捌月', '玖月', '拾月', '拾壹月', '拾贰月'],
['日', '壹', '贰', '叁', '肆', '伍', '陆']
)
language.yearSuffix = '年'
export default language

View File

@ -1,13 +0,0 @@
import Language from '../Language'
const language = new Language(
'Chinese',
['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
['日', '一', '二', '三', '四', '五', '六']
)
language.yearSuffix = '年'
export default language
// eslint-disable-next-line
;

View File

@ -1,9 +1,8 @@
<template>
<div
:tabindex="searchable ? -1 : tabindex"
:class="{'multiselect--active': isOpen, 'multiselect--disabled': disabled, 'multiselect--above': isAbove }"
:aria-owns="'listbox-'+id"
class="base-select multiselect"
:class="multiSelectStyle"
:aria-owns="'listbox-' + id"
role="combobox"
@focus="activate()"
@blur="searchable ? false : deactivate()"
@ -13,10 +12,10 @@
@keyup.esc="deactivate()"
>
<slot :toggle="toggle" name="caret">
<div class="multiselect__select" @mousedown.prevent.stop="toggle()" />
<div :class="multiselectSelectStyle" @mousedown.prevent.stop="toggle()" />
</slot>
<!-- <slot name="clear" :search="search"></slot> -->
<div ref="tags" :class="{'in-valid': invalid}" class="multiselect__tags">
<div ref="tags" :class="multiSelectTagsStyle">
<slot
:search="search"
:remove="removeElement"
@ -24,25 +23,41 @@
:is-open="isOpen"
name="selection"
>
<div v-show="visibleValues.length > 0" class="multiselect__tags-wrap">
<div
v-show="visibleValues.length > 0"
:class="multiselectTagsWrapStyle"
>
<template v-for="(option, index) of visibleValues" @mousedown.prevent>
<slot :option="option" :search="search" :remove="removeElement" name="tag">
<span :key="index" class="multiselect__tag">
<span v-text="getOptionLabel(option)"/>
<i class="multiselect__tag-icon" tabindex="1" @keypress.enter.prevent="removeElement(option)" @mousedown.prevent="removeElement(option)"/>
<slot
:option="option"
:search="search"
:remove="removeElement"
name="tag"
>
<span :key="index" :class="multiselectTagStyle">
<span v-text="getOptionLabel(option)" />
<i
:class="multiselectTagIconStyle"
tabindex="1"
@keypress.enter.prevent="removeElement(option)"
@mousedown.prevent="removeElement(option)"
/>
</span>
</slot>
</template>
</div>
<template v-if="internalValue && internalValue.length > limit">
<slot name="limit">
<strong class="multiselect__strong" v-text="limitText(internalValue.length - limit)"/>
<strong
:class="multiselectStrongStyle"
v-text="limitText(internalValue.length - limit)"
/>
</slot>
</template>
</slot>
<transition name="multiselect__loading">
<slot name="loading">
<div v-show="loading" class="multiselect__spinner"/>
<div v-show="loading" :class="multiselectSpinnerStyle" />
</slot>
</transition>
<input
@ -54,8 +69,8 @@
:value="search"
:disabled="disabled"
:tabindex="tabindex"
:aria-controls="'listbox-'+id"
:class="['multiselect__input']"
:aria-controls="'listbox-' + id"
:class="multiselectInputStyle"
type="text"
autocomplete="off"
spellcheck="false"
@ -67,10 +82,10 @@
@keydown.up.prevent="pointerBackward()"
@keypress.enter.prevent.stop.self="addPointerElement($event)"
@keydown.delete.stop="removeLastElement()"
>
/>
<span
v-if="isSingleLabelVisible"
class="multiselect__single"
:class="multiselectSingleStyle"
@mousedown.prevent="toggle"
>
<slot :option="singleValue" name="singleLabel">
@ -83,16 +98,23 @@
v-show="isOpen"
ref="list"
:style="{ maxHeight: optimizedHeight + 'px' }"
class="multiselect__content-wrapper"
:class="multiselectContentWrapperStyle"
tabindex="-1"
@focus="activate"
@mousedown.prevent
>
<ul :style="contentStyle" :id="'listbox-'+id" class="multiselect__content" role="listbox">
<slot name="beforeList"/>
<ul
:style="contentStyle"
:id="'listbox-' + id"
:class="multiselectContentStyle"
role="listbox"
>
<slot name="beforeList" />
<li v-if="multiple && max === internalValue.length">
<span class="multiselect__option">
<slot name="maxElements"> {{ $t('validation.maximum_options_error', { max: max }) }} </slot>
<span :class="multiselectOptionStyle">
<slot name="maxElements">
{{ $t('validation.maximum_options_error', { max: max }) }}
</slot>
</span>
</li>
<template v-if="!max || internalValue.length < max">
@ -100,16 +122,21 @@
v-for="(option, index) of filteredOptions"
:key="index"
:id="id + '-' + index"
:role="!(option && (option.$isLabel || option.$isDisabled)) ? 'option' : null"
class="multiselect__element"
:role="
!(option && (option.$isLabel || option.$isDisabled))
? 'option'
: null
"
:class="multiselectElementStyle"
>
<span
v-if="!(option && (option.$isLabel || option.$isDisabled))"
:class="optionHighlight(index, option)"
:data-select="option && option.isTag ? tagPlaceholder : selectLabelText"
:data-select="
option && option.isTag ? tagPlaceholder : selectLabelText
"
:data-selected="selectedLabelText"
:data-deselect="deselectLabelText"
class="multiselect__option"
@click.stop="select(option)"
@mouseenter.self="pointerSet(index)"
>
@ -122,7 +149,6 @@
:data-select="groupSelect && selectGroupLabelText"
:data-deselect="groupSelect && deselectGroupLabelText"
:class="groupHighlight(index, option)"
class="multiselect__option"
@mouseenter.self="groupSelect && pointerSet(index)"
@mousedown.prevent="selectGroup(option)"
>
@ -132,24 +158,50 @@
</span>
</li>
</template>
<li v-if="showNoOptions && (options.length === 0 && !search && !loading)">
<span class="multiselect__option">
<li
v-if="showNoOptions && (options.length === 0 && !search && !loading)"
>
<span :class="multiselectOptionStyle">
<slot name="noOptions">{{ $t('general.list_is_empty') }}</slot>
</span>
</li>
</ul>
<slot name="afterList"/>
<slot name="afterList" />
</div>
</transition>
</div>
</template>
<script>
import CraterTheme from '../theme/index'
import multiselectMixin from './multiselectMixin'
import pointerMixin from './pointerMixin'
const {
activeBaseSelectContainer,
disabledBaseSelectContainer,
baseSelectContainer,
multiSelect,
disabledMultiSelect,
multiSelectTags,
multiSelectTagsInvalid,
multiSelectTagsDefaultColor,
disabledMultiSelectTags,
multiselectTagsWrap,
multiselectTag,
multiselectTagIcon,
multiselectStrong,
multiselectSpinner,
multiselectInput,
multiselectSingle,
multiselectContentWrapper,
multiselectContent,
multiselectOption,
multiselectElement,
} = CraterTheme.BaseSelect
export default {
name: 'vue-multiselect',
name: 'VueMultiselect',
mixins: [multiselectMixin, pointerMixin],
props: {
/**
@ -159,7 +211,7 @@ export default {
*/
name: {
type: String,
default: ''
default: '',
},
/**
* String to show when pointing to an option
@ -168,7 +220,7 @@ export default {
*/
selectLabel: {
type: String,
default: ''
default: '',
},
/**
* String to show when pointing to an option
@ -177,7 +229,7 @@ export default {
*/
selectGroupLabel: {
type: String,
default: ''
default: '',
},
/**
* String to show next to selected option
@ -186,7 +238,7 @@ export default {
*/
selectedLabel: {
type: String,
default: 'Selected'
default: 'Selected',
},
/**
* String to show when pointing to an already selected option
@ -195,7 +247,7 @@ export default {
*/
deselectLabel: {
type: String,
default: 'Press enter to remove'
default: 'Press enter to remove',
},
/**
* String to show when pointing to an already selected option
@ -204,7 +256,7 @@ export default {
*/
deselectGroupLabel: {
type: String,
default: 'Press enter to deselect group'
default: 'Press enter to deselect group',
},
/**
* Decide whether to show pointer labels
@ -213,7 +265,7 @@ export default {
*/
showLabels: {
type: Boolean,
default: true
default: true,
},
/**
* Limit the display of selected options. The rest will be hidden within the limitText string.
@ -222,7 +274,7 @@ export default {
*/
limit: {
type: Number,
default: 99999
default: 99999,
},
/**
* Sets maxHeight style value of the dropdown
@ -231,7 +283,7 @@ export default {
*/
maxHeight: {
type: Number,
default: 300
default: 300,
},
/**
* Function that process the message shown when selected
@ -242,7 +294,7 @@ export default {
*/
limitText: {
type: Function,
default: count => `and ${count} more`
default: (count) => `and ${count} more`,
},
/**
* Set true to trigger the loading spinner.
@ -251,7 +303,7 @@ export default {
*/
loading: {
type: Boolean,
default: false
default: false,
},
/**
* Disables the multiselect if true.
@ -260,7 +312,7 @@ export default {
*/
disabled: {
type: Boolean,
default: false
default: false,
},
/**
* Fixed opening direction
@ -269,7 +321,7 @@ export default {
*/
openDirection: {
type: String,
default: ''
default: '',
},
/**
* Shows slot with message about empty options
@ -278,54 +330,54 @@ export default {
*/
showNoOptions: {
type: Boolean,
default: true
default: true,
},
showNoResults: {
type: Boolean,
default: true
default: true,
},
tabindex: {
type: Number,
default: 0
default: 0,
},
invalid: {
type: Boolean,
default: false
}
default: false,
},
},
computed: {
isSingleLabelVisible () {
isSingleLabelVisible() {
return (
(this.singleValue || this.singleValue === 0) &&
(!this.isOpen || !this.searchable) &&
!this.visibleValues.length
)
},
isPlaceholderVisible () {
isPlaceholderVisible() {
return !this.internalValue.length && (!this.searchable || !this.isOpen)
},
visibleValues () {
visibleValues() {
return this.multiple ? this.internalValue.slice(0, this.limit) : []
},
singleValue () {
singleValue() {
return this.internalValue[0]
},
deselectLabelText () {
deselectLabelText() {
return this.showLabels ? this.deselectLabel : ''
},
deselectGroupLabelText () {
deselectGroupLabelText() {
return this.showLabels ? this.deselectGroupLabel : ''
},
selectLabelText () {
selectLabelText() {
return this.showLabels ? this.selectLabel : ''
},
selectGroupLabelText () {
selectGroupLabelText() {
return this.showLabels ? this.selectGroupLabel : ''
},
selectedLabelText () {
selectedLabelText() {
return this.showLabels ? this.selectedLabel : ''
},
inputStyle () {
inputStyle() {
if (
this.searchable ||
(this.multiple && this.value && this.value.length)
@ -334,15 +386,17 @@ export default {
return this.isOpen
? { width: '100%' }
: ((this.value) ? { width: '0', position: 'absolute', padding: '0' } : '')
: this.value
? { width: '0', position: 'absolute', padding: '0' }
: ''
}
},
contentStyle () {
contentStyle() {
return this.options.length
? { display: 'inline-block' }
: { display: 'block' }
},
isAbove () {
isAbove() {
if (this.openDirection === 'above' || this.openDirection === 'top') {
return true
} else if (
@ -354,7 +408,7 @@ export default {
return this.preferredOpenDirection === 'above'
}
},
showSearchInput () {
showSearchInput() {
return (
this.searchable &&
(this.hasSingleSelectedSlot &&
@ -362,7 +416,466 @@ export default {
? this.isOpen
: true)
)
}
}
},
multiSelectStyle() {
let style = ['multiselect--active', baseSelectContainer]
if (this.isOpen) {
style.push(activeBaseSelectContainer)
}
if (this.disabled) {
style.push(disabledBaseSelectContainer)
}
if (this.isAbove) {
style.push('multiselect--above')
}
return style
},
multiselectSelectStyle() {
let style = [multiSelect]
if (this.disabled) {
style.push(disabledMultiSelect)
}
return style
},
multiSelectTagsStyle() {
let style = [multiSelectTags]
if (this.invalid) {
style.push(multiSelectTagsInvalid)
} else {
style.push(multiSelectTagsDefaultColor)
}
if (this.disabled) {
style.push(disabledMultiSelectTags)
}
return style
},
multiselectTagsWrapStyle() {
return [multiselectTagsWrap]
},
multiselectTagStyle() {
return [multiselectTag]
},
multiselectTagIconStyle() {
return [multiselectTagIcon]
},
multiselectStrongStyle() {
return [multiselectStrong]
},
multiselectSpinnerStyle() {
return [multiselectSpinner]
},
multiselectInputStyle() {
return [multiselectInput]
},
multiselectSingleStyle() {
return [multiselectSingle]
},
multiselectContentWrapperStyle() {
return [multiselectContentWrapper]
},
multiselectContentStyle() {
return [multiselectContent]
},
multiselectOptionStyle() {
return [multiselectOption]
},
multiselectElementStyle() {
return [multiselectElement]
},
},
}
</script>
<style lang="scss">
fieldset[disabled] .multiselect {
pointer-events: none;
}
.multiselect {
min-height: 40px;
}
.multiselect__spinner {
right: 1px;
top: 1px;
}
.multiselect__spinner:before,
.multiselect__spinner:after {
position: absolute;
content: '';
top: 50%;
left: 50%;
margin: -8px 0 0 -8px;
z-index: 5;
width: 16px;
height: 16px;
border-radius: 100%;
border-color: #41b883 transparent transparent;
border-style: solid;
border-width: 2px;
box-shadow: 0 0 0 1px transparent;
}
.multiselect__spinner:before {
animation: spinning 2.4s cubic-bezier(0.41, 0.26, 0.2, 0.62);
animation-iteration-count: infinite;
}
.multiselect__spinner:after {
animation: spinning 2.4s cubic-bezier(0.51, 0.09, 0.21, 0.8);
animation-iteration-count: infinite;
}
.multiselect__loading-enter-active,
.multiselect__loading-leave-active {
transition: opacity 0.4s ease-in-out;
opacity: 1;
}
.multiselect__loading-enter,
.multiselect__loading-leave-active {
opacity: 0;
}
.multiselect,
.multiselect__input,
.multiselect__single {
font-family: inherit;
// font-size: 14px;
touch-action: manipulation;
}
.multiselect {
box-sizing: content-box;
display: block;
position: relative;
width: 100%;
min-height: 40px;
text-align: left;
color: #35495e;
}
.multiselect * {
box-sizing: border-box;
}
.multiselect:focus {
border: 1px solid #817ae3 !important;
}
.multiselect--disabled {
pointer-events: none;
opacity: 0.6;
}
.multiselect--active:not(.multiselect--above) .multiselect__current,
.multiselect--active:not(.multiselect--above) .multiselect__input,
.multiselect--active:not(.multiselect--above) .multiselect__tags {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.multiselect--active .multiselect__select {
transform: rotateZ(180deg);
}
.multiselect--above.multiselect--active .multiselect__current,
.multiselect--above.multiselect--active .multiselect__input,
.multiselect--above.multiselect--active .multiselect__tags {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.multiselect__input,
.multiselect__single {
min-height: 20px;
transition: border 0.1s ease;
}
.multiselect__input::placeholder {
color: #b9c1d1;
}
.multiselect__tag ~ .multiselect__input,
.multiselect__tag ~ .multiselect__single {
width: auto;
}
.multiselect__input:hover,
.multiselect__single:hover {
border-color: #cfcfcf;
}
.multiselect__input:focus,
.multiselect__single:focus {
border-color: #a8a8a8;
outline: none;
}
.multiselect__tag {
background: #41b883;
text-overflow: ellipsis;
}
.multiselect__tag-icon {
font-style: initial;
}
.multiselect__tag-icon:after {
content: '×';
color: #266d4d;
font-size: 14px;
}
.multiselect__tag-icon:focus,
.multiselect__tag-icon:hover {
background: #369a6e;
}
.multiselect__tag-icon:focus:after,
.multiselect__tag-icon:hover:after {
color: white;
}
.multiselect__current {
line-height: 16px;
min-height: 40px;
box-sizing: border-box;
display: block;
overflow: hidden;
padding: 8px 12px 0;
padding-right: 30px;
white-space: nowrap;
margin: 0;
text-decoration: none;
border-radius: 5px;
border: 1px solid #ebf1fa;
cursor: pointer;
}
.multiselect__select {
right: 1px;
top: 1px;
transition: transform 0.2s;
}
.multiselect__select:before {
position: relative;
right: 0;
top: 65%;
color: #a5acc1;
margin-top: 4px;
border-style: solid;
border-width: 5px 5px 0 5px;
border-color: #a5acc1 transparent transparent transparent;
content: '';
}
.multiselect__placeholder {
color: #b9c1d1;
display: inline-block;
margin-bottom: 10px;
padding-top: 2px;
}
.multiselect--active .multiselect__placeholder {
display: none;
}
.multiselect__content-wrapper {
max-height: 240px;
-webkit-overflow-scrolling: touch;
}
.multiselect--above .multiselect__content-wrapper {
bottom: 100%;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
border-bottom: none;
border-top: 1px solid #e8e8e8;
}
.multiselect__content::webkit-scrollbar {
display: none;
}
.multiselect__option {
min-height: 40px;
}
.multiselect__option:after {
top: 0;
right: 0;
position: absolute;
line-height: 40px;
padding-right: 12px;
padding-left: 20px;
font-size: 13px;
}
.multiselect__option--highlight {
background: #41b883;
outline: none;
color: white;
}
.multiselect__option--highlight:after {
content: attr(data-select);
background: #41b883;
color: white;
}
.multiselect__option--selected {
background: #f3f3f3;
color: #35495e;
font-weight: bold;
}
.multiselect__option--selected:after {
content: attr(data-selected);
color: silver;
}
.multiselect__option--selected.multiselect__option--highlight {
background: #ff6a6a;
color: #fff;
}
.multiselect__option--selected.multiselect__option--highlight:after {
background: #ff6a6a;
content: attr(data-deselect);
color: #fff;
}
.multiselect--disabled .multiselect__current,
.multiselect--disabled .multiselect__select {
background: #ebf1fa;
color: #b9c1d1;
}
.multiselect--disabled .multiselect__input,
.multiselect--disabled .multiselect__single {
background: #ebf1fa;
color: #b9c1d1;
}
.multiselect__option--disabled {
background: transparent !important;
color: #dddddd !important;
cursor: text;
pointer-events: none;
}
.multiselect__option--group {
background: #ededed;
color: #35495e;
}
.multiselect__option--group.multiselect__option--highlight {
background: #35495e;
color: #fff;
}
.multiselect__option--group.multiselect__option--highlight:after {
background: #35495e;
}
.multiselect__option--disabled.multiselect__option--highlight {
background: #dedede;
}
.multiselect__option--group-selected.multiselect__option--highlight {
background: #ff6a6a;
color: #fff;
}
.multiselect__option--group-selected.multiselect__option--highlight:after {
background: #ff6a6a;
content: attr(data-deselect);
color: #fff;
}
.multiselect-enter-active,
.multiselect-leave-active {
transition: all 0.15s ease;
}
.multiselect-enter,
.multiselect-leave-active {
opacity: 0;
}
*[dir='rtl'] .multiselect {
text-align: right;
}
*[dir='rtl'] .multiselect__select {
right: auto;
left: 1px;
}
*[dir='rtl'] .multiselect__tags {
padding: 8px 8px 0px 40px;
}
*[dir='rtl'] .multiselect__content {
text-align: right;
}
*[dir='rtl'] .multiselect__option:after {
right: auto;
left: 0;
}
*[dir='rtl'] .multiselect__clear {
right: auto;
left: 12px;
}
*[dir='rtl'] .multiselect__spinner {
right: auto;
left: 1px;
}
@keyframes spinning {
from {
transform: rotate(0);
}
to {
transform: rotate(2turn);
}
}
.multiselect {
.multiselect__option--highlight {
background: #5851d8;
color: #040405;
font-weight: normal !important;
&.multiselect__option--selected {
background: #ebf1fa;
color: #040405;
font-size: 1rem;
font-weight: normal !important;
&::after {
background: #040405;
color: #fff;
}
}
&::after {
background: #040405;
color: #fff;
}
}
.multiselect__option--selected {
font-weight: normal !important;
background: #ebf1fa;
}
.multiselect__tags-wrap .multiselect__tag {
background: #5851d8;
color: #040405;
.multiselect__tag-icon {
&:hover {
background: #5851d8;
}
&::after {
color: #040405;
}
}
}
&.error {
border: 1px solid #fb7178;
border-radius: 5px;
}
}
</style>

View File

@ -1,14 +1,14 @@
function isEmpty (opt) {
function isEmpty(opt) {
if (opt === 0) return false
if (Array.isArray(opt) && opt.length === 0) return true
return !opt
}
function not (fun) {
function not(fun) {
return (...params) => !fun(...params)
}
function includes (str, query) {
function includes(str, query) {
/* istanbul ignore else */
if (str === undefined) str = 'undefined'
if (str === null) str = 'null'
@ -17,22 +17,24 @@ function includes (str, query) {
return text.indexOf(query.trim()) !== -1
}
function filterOptions (options, search, label, customLabel) {
return options.filter(option => includes(customLabel(option, label), search))
function filterOptions(options, search, label, customLabel) {
return options.filter((option) =>
includes(customLabel(option, label), search)
)
}
function stripGroups (options) {
return options.filter(option => !option.$isLabel)
function stripGroups(options) {
return options.filter((option) => !option.$isLabel)
}
function flattenOptions (values, label) {
function flattenOptions(values, label) {
return (options) =>
options.reduce((prev, curr) => {
/* istanbul ignore else */
if (curr[values] && curr[values].length) {
prev.push({
$groupLabel: curr[label],
$isLabel: true
$isLabel: true,
})
return prev.concat(curr[values])
}
@ -40,40 +42,47 @@ function flattenOptions (values, label) {
}, [])
}
function filterGroups (search, label, values, groupLabel, customLabel) {
function filterGroups(search, label, values, groupLabel, customLabel) {
return (groups) =>
groups.map(group => {
groups.map((group) => {
/* istanbul ignore else */
if (!group[values]) {
console.warn(`Options passed to vue-multiselect do not contain groups, despite the config.`)
console.warn(
`Options passed to vue-multiselect do not contain groups, despite the config.`
)
return []
}
const groupOptions = filterOptions(group[values], search, label, customLabel)
const groupOptions = filterOptions(
group[values],
search,
label,
customLabel
)
return groupOptions.length
? {
[groupLabel]: group[groupLabel],
[values]: groupOptions
}
[groupLabel]: group[groupLabel],
[values]: groupOptions,
}
: []
})
}
const flow = (...fns) => x => fns.reduce((v, f) => f(v), x)
const flow = (...fns) => (x) => fns.reduce((v, f) => f(v), x)
export default {
data () {
data() {
return {
search: '',
isOpen: false,
preferredOpenDirection: 'below',
optimizedHeight: this.maxHeight
optimizedHeight: this.maxHeight,
}
},
props: {
initialSearch: {
type: String,
default: ''
default: '',
},
/**
* Decide whether to filter the results based on search query.
@ -82,7 +91,7 @@ export default {
*/
internalSearch: {
type: Boolean,
default: true
default: true,
},
/**
* Array of available options: Objects, Strings or Integers.
@ -92,7 +101,7 @@ export default {
*/
options: {
type: Array,
required: true
required: true,
},
/**
* Equivalent to the `multiple` attribute on a `<select>` input.
@ -101,7 +110,7 @@ export default {
*/
multiple: {
type: Boolean,
default: false
default: false,
},
/**
* Presets the selected options value.
@ -109,9 +118,9 @@ export default {
*/
value: {
type: null,
default () {
default() {
return []
}
},
},
/**
* Key to compare objects
@ -119,7 +128,7 @@ export default {
* @type {String}
*/
trackBy: {
type: String
type: String,
},
/**
* Label to look for in option Object
@ -127,7 +136,7 @@ export default {
* @type {String}
*/
label: {
type: String
type: String,
},
/**
* Enable/disable search in options
@ -136,7 +145,7 @@ export default {
*/
searchable: {
type: Boolean,
default: true
default: true,
},
/**
* Clear the search input after `)
@ -145,7 +154,7 @@ export default {
*/
clearOnSelect: {
type: Boolean,
default: true
default: true,
},
/**
* Hide already selected options
@ -154,7 +163,7 @@ export default {
*/
hideSelected: {
type: Boolean,
default: false
default: false,
},
/**
* Equivalent to the `placeholder` attribute on a `<select>` input.
@ -163,7 +172,7 @@ export default {
*/
placeholder: {
type: String,
default: 'Select option'
default: 'Select option',
},
/**
* Allow to remove all selected values
@ -172,7 +181,7 @@ export default {
*/
allowEmpty: {
type: Boolean,
default: true
default: true,
},
/**
* Reset this.internalValue, this.search after this.internalValue changes.
@ -182,7 +191,7 @@ export default {
*/
resetAfter: {
type: Boolean,
default: false
default: false,
},
/**
* Enable/disable closing after selecting an option
@ -191,7 +200,7 @@ export default {
*/
closeOnSelect: {
type: Boolean,
default: true
default: true,
},
/**
* Function to interpolate the custom label
@ -200,10 +209,10 @@ export default {
*/
customLabel: {
type: Function,
default (option, label) {
default(option, label) {
if (isEmpty(option)) return ''
return label ? option[label] : option
}
},
},
/**
* Disable / Enable tagging
@ -212,16 +221,16 @@ export default {
*/
taggable: {
type: Boolean,
default: false
default: false,
},
/**
* String to show when highlighting a potential tag
* @default 'Press enter to create a tag'
* @type {String}
*/
*/
tagPlaceholder: {
type: String,
default: 'Press enter to create a tag'
default: 'Press enter to create a tag',
},
/**
* By default new tags will appear above the search results.
@ -229,56 +238,56 @@ export default {
* and will proritize the search results
* @default 'top'
* @type {String}
*/
*/
tagPosition: {
type: String,
default: 'top'
default: 'top',
},
/**
* Number of allowed selected options. No limit if 0.
* @default 0
* @type {Number}
*/
*/
max: {
type: [Number, Boolean],
default: false
default: false,
},
/**
* Will be passed with all events as second param.
* Useful for identifying events origin.
* @default null
* @type {String|Integer}
*/
*/
id: {
default: null
default: null,
},
/**
* Limits the options displayed in the dropdown
* to the first X options.
* @default 1000
* @type {Integer}
*/
*/
optionsLimit: {
type: Number,
default: 1000
default: 1000,
},
/**
* Name of the property containing
* the group values
* @default 1000
* @type {String}
*/
*/
groupValues: {
type: String
type: String,
},
/**
* Name of the property containing
* the group label
* @default 1000
* @type {String}
*/
*/
groupLabel: {
type: String
type: String,
},
/**
* Allow to select all group values
@ -288,43 +297,45 @@ export default {
*/
groupSelect: {
type: Boolean,
default: false
default: false,
},
/**
* Array of keyboard keys to block
* when selecting
* @default 1000
* @type {String}
*/
*/
blockKeys: {
type: Array,
default () {
default() {
return []
}
},
},
/**
* Prevent from wiping up the search value
* @default false
* @type {Boolean}
*/
*/
preserveSearch: {
type: Boolean,
default: false
default: false,
},
/**
* Select 1st options if value is empty
* @default false
* @type {Boolean}
*/
*/
preselectFirst: {
type: Boolean,
default: false
}
default: false,
},
},
mounted () {
mounted() {
/* istanbul ignore else */
if (!this.multiple && this.max) {
console.warn('[Vue-Multiselect warn]: Max prop should not be used when prop Multiple equals false.')
console.warn(
'[Vue-Multiselect warn]: Max prop should not be used when prop Multiple equals false.'
)
}
if (
this.preselectFirst &&
@ -339,12 +350,14 @@ export default {
}
},
computed: {
internalValue () {
internalValue() {
return this.value || this.value === 0
? Array.isArray(this.value) ? this.value : [this.value]
? Array.isArray(this.value)
? this.value
: [this.value]
: []
},
filteredOptions () {
filteredOptions() {
const search = this.search || ''
const normalizedSearch = search.toLowerCase().trim()
@ -354,9 +367,16 @@ export default {
if (this.internalSearch) {
options = this.groupValues
? this.filterAndFlat(options, normalizedSearch, this.label)
: filterOptions(options, normalizedSearch, this.label, this.customLabel)
: filterOptions(
options,
normalizedSearch,
this.label,
this.customLabel
)
} else {
options = this.groupValues ? flattenOptions(this.groupValues, this.groupLabel)(options) : options
options = this.groupValues
? flattenOptions(this.groupValues, this.groupLabel)(options)
: options
}
options = this.hideSelected
@ -364,7 +384,11 @@ export default {
: options
/* istanbul ignore else */
if (this.taggable && normalizedSearch.length && !this.isExistingOption(normalizedSearch)) {
if (
this.taggable &&
normalizedSearch.length &&
!this.isExistingOption(normalizedSearch)
) {
if (this.tagPosition === 'bottom') {
options.push({ isTag: true, label: search })
} else {
@ -374,57 +398,71 @@ export default {
return options.slice(0, this.optionsLimit)
},
valueKeys () {
valueKeys() {
if (this.trackBy) {
return this.internalValue.map(element => element[this.trackBy])
return this.internalValue.map((element) => element[this.trackBy])
} else {
return this.internalValue
}
},
optionKeys () {
const options = this.groupValues ? this.flatAndStrip(this.options) : this.options
return options.map(element => this.customLabel(element, this.label).toString().toLowerCase())
optionKeys() {
const options = this.groupValues
? this.flatAndStrip(this.options)
: this.options
return options.map((element) =>
this.customLabel(element, this.label).toString().toLowerCase()
)
},
currentOptionLabel () {
currentOptionLabel() {
return this.multiple
? this.searchable ? '' : this.placeholder
? this.searchable
? ''
: this.placeholder
: this.internalValue.length
? this.getOptionLabel(this.internalValue[0])
: this.searchable ? '' : this.placeholder
}
? this.getOptionLabel(this.internalValue[0])
: this.searchable
? ''
: this.placeholder
},
},
watch: {
internalValue () {
internalValue() {
/* istanbul ignore else */
if (this.resetAfter && this.internalValue.length) {
this.search = ''
this.$emit('input', this.multiple ? [] : null)
}
},
search () {
search() {
this.$emit('search-change', this.search, this.id)
}
},
},
methods: {
/**
* Returns the internalValue in a way it can be emited to the parent
* @returns {Object||Array||String||Integer}
*/
getValue () {
getValue() {
return this.multiple
? this.internalValue
: this.internalValue.length === 0
? null
: this.internalValue[0]
? null
: this.internalValue[0]
},
/**
* Filters and then flattens the options list
* @param {Array}
* @returns {Array} returns a filtered and flat options list
*/
filterAndFlat (options, search, label) {
filterAndFlat(options, search, label) {
return flow(
filterGroups(search, label, this.groupValues, this.groupLabel, this.customLabel),
filterGroups(
search,
label,
this.groupValues,
this.groupLabel,
this.customLabel
),
flattenOptions(this.groupValues, this.groupLabel)
)(options)
},
@ -433,7 +471,7 @@ export default {
* @param {Array}
* @returns {Array} returns a flat options list without group labels
*/
flatAndStrip (options) {
flatAndStrip(options) {
return flow(
flattenOptions(this.groupValues, this.groupLabel),
stripGroups
@ -443,7 +481,7 @@ export default {
* Updates the search value
* @param {String}
*/
updateSearch (query) {
updateSearch(query) {
this.search = query
this.$emit('value', this.search)
},
@ -453,10 +491,8 @@ export default {
* @param {String}
* @returns {Boolean} returns true if element is available
*/
isExistingOption (query) {
return !this.options
? false
: this.optionKeys.indexOf(query) > -1
isExistingOption(query) {
return !this.options ? false : this.optionKeys.indexOf(query) > -1
},
/**
* Finds out if the given element is already present
@ -464,10 +500,8 @@ export default {
* @param {Object||String||Integer} option passed element to check
* @returns {Boolean} returns true if element is selected
*/
isSelected (option) {
const opt = this.trackBy
? option[this.trackBy]
: option
isSelected(option) {
const opt = this.trackBy ? option[this.trackBy] : option
return this.valueKeys.indexOf(opt) > -1
},
/**
@ -475,7 +509,7 @@ export default {
* @param {Object||String||Integer} option passed element to check
* @returns {Boolean} returns true if element is disabled
*/
isOptionDisabled (option) {
isOptionDisabled(option) {
return !!option.$isDisabled
},
/**
@ -486,7 +520,7 @@ export default {
* @param {Object||String||Integer} Passed option
* @returns {Object||String}
*/
getOptionLabel (option) {
getOptionLabel(option) {
if (isEmpty(option)) return ''
/* istanbul ignore else */
if (option.isTag) return option.label
@ -506,19 +540,22 @@ export default {
* @param {Object||String||Integer} option to select/deselect
* @param {Boolean} block removing
*/
select (option, key) {
select(option, key) {
/* istanbul ignore else */
if (option.$isLabel && this.groupSelect) {
this.selectGroup(option)
return
}
if (this.blockKeys.indexOf(key) !== -1 ||
if (
this.blockKeys.indexOf(key) !== -1 ||
this.disabled ||
option.$isDisabled ||
option.$isLabel
) return
)
return
/* istanbul ignore else */
if (this.max && this.multiple && this.internalValue.length === this.max) return
if (this.max && this.multiple && this.internalValue.length === this.max)
return
/* istanbul ignore else */
if (key === 'Tab' && !this.pointerDirty) return
if (option.isTag) {
@ -553,8 +590,8 @@ export default {
*
* @param {Object||String||Integer} group to select/deselect
*/
selectGroup (selectedGroup) {
const group = this.options.find(option => {
selectGroup(selectedGroup) {
const group = this.options.find((option) => {
return option[this.groupLabel] === selectedGroup.$groupLabel
})
@ -564,21 +601,18 @@ export default {
this.$emit('remove', group[this.groupValues], this.id)
const newValue = this.internalValue.filter(
option => group[this.groupValues].indexOf(option) === -1
(option) => group[this.groupValues].indexOf(option) === -1
)
this.$emit('input', newValue, this.id)
} else {
const optionsToAdd = group[this.groupValues].filter(
option => !(this.isOptionDisabled(option) || this.isSelected(option))
(option) =>
!(this.isOptionDisabled(option) || this.isSelected(option))
)
this.$emit('select', optionsToAdd, this.id)
this.$emit(
'input',
this.internalValue.concat(optionsToAdd),
this.id
)
this.$emit('input', this.internalValue.concat(optionsToAdd), this.id)
}
},
/**
@ -586,8 +620,9 @@ export default {
*
* @param {Object} group to validated selected values against
*/
wholeGroupSelected (group) {
return group[this.groupValues].every(option => this.isSelected(option) || this.isOptionDisabled(option)
wholeGroupSelected(group) {
return group[this.groupValues].every(
(option) => this.isSelected(option) || this.isOptionDisabled(option)
)
},
/**
@ -595,7 +630,7 @@ export default {
*
* @param {Object} group to check for disabled values
*/
wholeGroupDisabled (group) {
wholeGroupDisabled(group) {
return group[this.groupValues].every(this.isOptionDisabled)
},
/**
@ -606,7 +641,7 @@ export default {
* @param {type} option description
* @returns {type} description
*/
removeElement (option, shouldClose = true) {
removeElement(option, shouldClose = true) {
/* istanbul ignore else */
if (this.disabled) return
/* istanbul ignore else */
@ -617,13 +652,16 @@ export default {
return
}
const index = typeof option === 'object'
? this.valueKeys.indexOf(option[this.trackBy])
: this.valueKeys.indexOf(option)
const index =
typeof option === 'object'
? this.valueKeys.indexOf(option[this.trackBy])
: this.valueKeys.indexOf(option)
this.$emit('remove', option, this.id)
if (this.multiple) {
const newValue = this.internalValue.slice(0, index).concat(this.internalValue.slice(index + 1))
const newValue = this.internalValue
.slice(0, index)
.concat(this.internalValue.slice(index + 1))
this.$emit('input', newValue, this.id)
} else {
this.$emit('input', null, this.id)
@ -638,25 +676,36 @@ export default {
*
* @fires this#removeElement
*/
removeLastElement () {
removeLastElement() {
/* istanbul ignore else */
if (this.blockKeys.indexOf('Delete') !== -1) return
/* istanbul ignore else */
if (this.search.length === 0 && Array.isArray(this.internalValue) && this.internalValue.length) {
this.removeElement(this.internalValue[this.internalValue.length - 1], false)
if (
this.search.length === 0 &&
Array.isArray(this.internalValue) &&
this.internalValue.length
) {
this.removeElement(
this.internalValue[this.internalValue.length - 1],
false
)
}
},
/**
* Opens the multiselects dropdown.
* Sets this.isOpen to TRUE
*/
activate () {
activate() {
/* istanbul ignore else */
if (this.isOpen || this.disabled) return
this.adjustPosition()
/* istanbul ignore else */
if (this.groupValues && this.pointer === 0 && this.filteredOptions.length) {
if (
this.groupValues &&
this.pointer === 0 &&
this.filteredOptions.length
) {
this.pointer = 1
}
@ -674,7 +723,7 @@ export default {
* Closes the multiselects dropdown.
* Sets this.isOpen to FALSE
*/
deactivate () {
deactivate() {
/* istanbul ignore else */
if (!this.isOpen) return
this.isOpen = false
@ -694,29 +743,33 @@ export default {
* @fires this#activate || this#deactivate
* @property {Boolean} isOpen indicates if dropdown is open
*/
toggle () {
this.isOpen
? this.deactivate()
: this.activate()
toggle() {
this.isOpen ? this.deactivate() : this.activate()
},
/**
* Updates the hasEnoughSpace variable used for
* detecting where to expand the dropdown
*/
adjustPosition () {
adjustPosition() {
if (typeof window === 'undefined') return
const spaceAbove = this.$el.getBoundingClientRect().top
const spaceBelow = window.innerHeight - this.$el.getBoundingClientRect().bottom
const spaceBelow =
window.innerHeight - this.$el.getBoundingClientRect().bottom
const hasEnoughSpaceBelow = spaceBelow > this.maxHeight
if (hasEnoughSpaceBelow || spaceBelow > spaceAbove || this.openDirection === 'below' || this.openDirection === 'bottom') {
if (
hasEnoughSpaceBelow ||
spaceBelow > spaceAbove ||
this.openDirection === 'below' ||
this.openDirection === 'bottom'
) {
this.preferredOpenDirection = 'below'
this.optimizedHeight = Math.min(spaceBelow - 40, this.maxHeight)
} else {
this.preferredOpenDirection = 'above'
this.optimizedHeight = Math.min(spaceAbove - 40, this.maxHeight)
}
}
}
},
},
}

View File

@ -1,8 +1,10 @@
import CraterTheme from '../theme/index'
const { multiselectOption } = CraterTheme.BaseSelect
export default {
data () {
data() {
return {
pointer: 0,
pointerDirty: false
pointerDirty: false,
}
},
props: {
@ -13,79 +15,106 @@ export default {
*/
showPointer: {
type: Boolean,
default: true
default: true,
},
optionHeight: {
type: Number,
default: 40
}
default: 40,
},
},
computed: {
pointerPosition () {
pointerPosition() {
return this.pointer * this.optionHeight
},
visibleElements () {
visibleElements() {
return this.optimizedHeight / this.optionHeight
}
},
},
watch: {
filteredOptions () {
filteredOptions() {
this.pointerAdjust()
},
isOpen () {
isOpen() {
this.pointerDirty = false
},
pointer () {
this.$refs.search.setAttribute('aria-activedescendant', this.id + '-' + this.pointer.toString())
}
pointer() {
this.$refs.search.setAttribute(
'aria-activedescendant',
this.id + '-' + this.pointer.toString()
)
},
},
methods: {
optionHighlight (index, option) {
return {
'multiselect__option--highlight': index === this.pointer && this.showPointer,
'multiselect__option--selected': this.isSelected(option)
}
optionHighlight(index, option) {
return [
{
'multiselect__option--highlight':
index === this.pointer && this.showPointer,
'multiselect__option--selected': this.isSelected(option),
},
multiselectOption,
]
},
groupHighlight (index, selectedGroup) {
groupHighlight(index, selectedGroup) {
if (!this.groupSelect) {
return ['multiselect__option--group', 'multiselect__option--disabled']
return [
'multiselect__option--group',
'multiselect__option--disabled',
multiselectOption,
]
}
const group = this.options.find(option => {
const group = this.options.find((option) => {
return option[this.groupLabel] === selectedGroup.$groupLabel
})
return group && !this.wholeGroupDisabled(group) ? [
'multiselect__option--group',
{ 'multiselect__option--highlight': index === this.pointer && this.showPointer },
{ 'multiselect__option--group-selected': this.wholeGroupSelected(group) }
] : 'multiselect__option--disabled'
return group && !this.wholeGroupDisabled(group)
? [
'multiselect__option--group',
{
'multiselect__option--highlight':
index === this.pointer && this.showPointer,
},
{
'multiselect__option--group-selected': this.wholeGroupSelected(
group
),
},
multiselectOption,
]
: ['multiselect__option--disabled', multiselectOption]
},
addPointerElement ({ key } = 'Enter') {
addPointerElement({ key } = 'Enter') {
/* istanbul ignore else */
if (this.filteredOptions.length > 0) {
this.select(this.filteredOptions[this.pointer], key)
}
this.pointerReset()
},
pointerForward () {
pointerForward() {
/* istanbul ignore else */
if (this.pointer < this.filteredOptions.length - 1) {
this.pointer++
/* istanbul ignore next */
if (this.$refs.list.scrollTop <= this.pointerPosition - (this.visibleElements - 1) * this.optionHeight) {
this.$refs.list.scrollTop = this.pointerPosition - (this.visibleElements - 1) * this.optionHeight
if (
this.$refs.list.scrollTop <=
this.pointerPosition - (this.visibleElements - 1) * this.optionHeight
) {
this.$refs.list.scrollTop =
this.pointerPosition -
(this.visibleElements - 1) * this.optionHeight
}
/* istanbul ignore else */
if (
this.filteredOptions[this.pointer] &&
this.filteredOptions[this.pointer].$isLabel &&
!this.groupSelect
) this.pointerForward()
)
this.pointerForward()
}
this.pointerDirty = true
},
pointerBackward () {
pointerBackward() {
if (this.pointer > 0) {
this.pointer--
/* istanbul ignore else */
@ -97,18 +126,20 @@ export default {
this.filteredOptions[this.pointer] &&
this.filteredOptions[this.pointer].$isLabel &&
!this.groupSelect
) this.pointerBackward()
)
this.pointerBackward()
} else {
/* istanbul ignore else */
if (
this.filteredOptions[this.pointer] &&
this.filteredOptions[0].$isLabel &&
!this.groupSelect
) this.pointerForward()
)
this.pointerForward()
}
this.pointerDirty = true
},
pointerReset () {
pointerReset() {
/* istanbul ignore else */
if (!this.closeOnSelect) return
this.pointer = 0
@ -117,7 +148,7 @@ export default {
this.$refs.list.scrollTop = 0
}
},
pointerAdjust () {
pointerAdjust() {
/* istanbul ignore else */
if (this.pointer >= this.filteredOptions.length - 1) {
this.pointer = this.filteredOptions.length
@ -125,16 +156,17 @@ export default {
: 0
}
if (this.filteredOptions.length > 0 &&
if (
this.filteredOptions.length > 0 &&
this.filteredOptions[this.pointer].$isLabel &&
!this.groupSelect
) {
this.pointerForward()
}
},
pointerSet (index) {
pointerSet(index) {
this.pointer = index
this.pointerDirty = true
}
}
},
},
}

View File

@ -1,64 +0,0 @@
import { pick } from '../helpers'
export default class Column {
constructor (columnComponent) {
const properties = pick(columnComponent, [
'show', 'label', 'dataType', 'sortable', 'sortBy', 'filterable',
'filterOn', 'hidden', 'formatter', 'cellClass', 'headerClass', 'sortAs'
])
for (const property in properties) {
this[property] = columnComponent[property]
}
this.template = columnComponent.$scopedSlots.default
}
isFilterable () {
return this.filterable
}
getFilterFieldName () {
return this.filterOn || this.show
}
isSortable () {
return this.sortable
}
getSortPredicate (sortOrder, allColumns) {
const sortFieldName = this.getSortFieldName()
const sortColumn = allColumns.find(column => (column.sortAs === sortFieldName || column.show === sortFieldName))
const dataType = sortColumn.dataType
if (dataType.startsWith('date') || dataType === 'numeric') {
return (row1, row2) => {
const value1 = row1.getSortableValue(sortFieldName)
const value2 = row2.getSortableValue(sortFieldName)
if (sortOrder === 'desc') {
return value2 < value1 ? -1 : 1
}
return value1 < value2 ? -1 : 1
}
}
return (row1, row2) => {
const value1 = row1.getSortableValue(sortFieldName)
const value2 = row2.getSortableValue(sortFieldName)
if (sortOrder === 'desc') {
return value2.localeCompare(value1)
}
return value1.localeCompare(value2)
}
}
getSortFieldName () {
return this.sortBy || this.sortAs || this.show
}
}

View File

@ -1,61 +0,0 @@
import moment from 'moment'
import { get } from '../helpers'
export default class Row {
constructor (data, columns) {
this.data = data
this.columns = columns
}
getValue (columnName) {
return get(this.data, columnName)
}
getColumn (columnName) {
return this.columns.find(column => (column.show === columnName || column.sortAs === columnName))
}
getFilterableValue (columnName) {
const value = this.getValue(columnName)
if (!value) {
return ''
}
return value.toString().toLowerCase()
}
getSortableValue (columnName) {
const dataType = this.getColumn(columnName).dataType
let value = this.getValue(columnName)
if (value === undefined || value === null) {
return ''
}
if (value instanceof String) {
value = value.toLowerCase()
}
if (dataType.startsWith('date')) {
const format = dataType.replace('date:', '')
return moment(value, format).format('YYYYMMDDHHmmss')
}
if (dataType === 'numeric') {
return value
}
return value.toString()
}
passesFilter (filter) {
return this.columns
.filter(column => column.isFilterable())
.map(column => this.getFilterableValue(column.getFilterFieldName()))
.filter(filterableValue => filterableValue.indexOf(filter.toLowerCase()) >= 0)
.length
}
}

View File

@ -1,120 +0,0 @@
<template>
<nav v-if="shouldShowPagination">
<ul class="pagination justify-content-center">
<li :class="{ disabled: pagination.currentPage === 1 }">
<a
:class="{ disabled: pagination.currentPage === 1 }"
@click="pageClicked( pagination.currentPage - 1 )"
>
<i class="left chevron icon">«</i>
</a>
</li>
<li v-if="hasFirst" :class="{ active: isActive(1) }" class="page-item">
<a class="page-link" @click="pageClicked(1)">1</a>
</li>
<li v-if="hasFirstEllipsis"><span class="pagination-ellipsis">&hellip;</span></li>
<li v-for="page in pages" :key="page" :class="{ active: isActive(page), disabled: page === '...' }" class="page-item">
<a class="page-link" @click="pageClicked(page)">{{ page }}</a>
</li>
<li v-if="hasLastEllipsis"><span class="pagination-ellipsis">&hellip;</span></li>
<li
v-if="hasLast"
:class="{ active: isActive(this.pagination.totalPages) }"
class="page-item"
>
<a class="page-link" @click="pageClicked(pagination.totalPages)">
{{ pagination.totalPages }}
</a>
</li>
<li>
<a
:class="{ disabled: pagination.currentPage === pagination.totalPages }"
@click="pageClicked( pagination.currentPage + 1 )"
>
<i class="right chevron icon">»</i>
</a>
</li>
</ul>
</nav>
</template>
<script>
export default {
props: {
pagination: {
type: Object,
default: () => ({})
}
},
computed: {
pages () {
return this.pagination.totalPages === undefined
? []
: this.pageLinks()
},
hasFirst () {
return this.pagination.currentPage >= 4 || this.pagination.totalPages < 10
},
hasLast () {
return this.pagination.currentPage <= this.pagination.totalPages - 3 || this.pagination.totalPages < 10
},
hasFirstEllipsis () {
return this.pagination.currentPage >= 4 && this.pagination.totalPages >= 10
},
hasLastEllipsis () {
return this.pagination.currentPage <= this.pagination.totalPages - 3 && this.pagination.totalPages >= 10
},
shouldShowPagination () {
if (this.pagination.totalPages === undefined) {
return false
}
if (this.pagination.count === 0) {
return false
}
return this.pagination.totalPages > 1
}
},
methods: {
isActive (page) {
const currentPage = this.pagination.currentPage || 1
return currentPage === page
},
pageClicked (page) {
if (page === '...' ||
page === this.pagination.currentPage ||
page > this.pagination.totalPages ||
page < 1) {
return
}
this.$emit('pageChange', page)
},
pageLinks () {
const pages = []
let left = 2
let right = this.pagination.totalPages - 1
if (this.pagination.totalPages >= 10) {
left = Math.max(1, this.pagination.currentPage - 2)
right = Math.min(this.pagination.currentPage + 2, this.pagination.totalPages)
}
for (let i = left; i <= right; i++) {
pages.push(i)
}
return pages
}
}
}
</script>

View File

@ -1,24 +0,0 @@
export default {
functional: true,
props: ['column', 'row', 'responsiveLabel'],
render (createElement, { props }) {
const data = {}
if (props.column.cellClass) {
data.class = props.column.cellClass
}
if (props.column.template) {
return createElement('td', data, props.column.template(props.row.data))
}
data.domProps = {}
data.domProps.innerHTML = props.column.formatter(props.row.getValue(props.column.show), props.row.data)
return createElement('td', [
createElement('span', props.responsiveLabel), data.domProps.innerHTML
])
}
}

View File

@ -1,32 +0,0 @@
<template>
<!-- Never render the contents -->
<!-- The scoped slot won't have the required data -->
<div v-if="false">
<slot></slot>
</div>
</template>
<script>
import settings from '../settings'
export default {
props: {
show: { required: false, type: String },
label: { default: null, type: String },
dataType: { default: 'string', type: String },
sortable: { default: true, type: Boolean },
sortBy: { default: null },
filterable: { default: true, type: Boolean },
sortAs: { default: null },
filterOn: { default: null },
formatter: { default: v => v, type: Function },
hidden: { default: false, type: Boolean },
cellClass: { default: settings.cellClass },
headerClass: { default: settings.headerClass },
}
}
</script>

View File

@ -1,72 +0,0 @@
<template>
<th
v-if="this.isVisible"
slot-scope="col"
:aria-sort="ariaSort"
:aria-disabled="ariaDisabled"
:class="headerClass"
role="columnheader"
@click="clicked"
>
{{ label }}
</th>
</template>
<script>
import { classList } from '../helpers'
export default {
props: ['column', 'sort'],
computed: {
ariaDisabled () {
if (!this.column.isSortable()) {
return 'true'
}
return false
},
ariaSort () {
if (!this.column.isSortable ()) {
return false
}
if ((this.column.sortAs || this.column.show) !== this.sort.fieldName) {
return 'none'
}
return this.sort.order === 'asc' ? 'ascending' : 'descending';
},
headerClass () {
if (!this.column.isSortable()) {
return classList('table-component__th', this.column.headerClass);
}
if ((this.column.sortAs || this.column.show) !== this.sort.fieldName) {
return classList('table-component__th table-component__th--sort', this.column.headerClass);
}
return classList(`table-component__th table-component__th--sort-${this.sort.order}`, this.column.headerClass);
},
isVisible () {
return !this.column.hidden
},
label () {
if (this.column.label === null) {
return this.column.show
}
return this.column.label
}
},
methods: {
clicked () {
if (this.column.isSortable()) {
this.$emit('click', this.column)
}
}
}
}
</script>

View File

@ -1,330 +0,0 @@
<template>
<div class="table-component">
<div v-if="showFilter && filterableColumnExists" class="table-component__filter">
<input
:class="fullFilterInputClass"
v-model="filter"
:placeholder="filterPlaceholder"
type="text"
>
<a v-if="filter" class="table-component__filter__clear" @click="filter = ''">×</a>
</div>
<div class="table-component__table-wrapper">
<base-loader v-if="loading" class="table-loader" />
<table :class="fullTableClass">
<caption
v-if="showCaption"
class="table-component__table__caption"
role="alert"
aria-live="polite"
>{{ ariaCaption }}</caption>
<thead :class="fullTableHeadClass">
<tr>
<table-column-header
v-for="column in columns"
:key="column.show || column.show"
:sort="sort"
:column="column"
@click="changeSorting"
/>
</tr>
</thead>
<tbody :class="fullTableBodyClass">
<table-row
v-for="row in displayedRows"
:key="row.vueTableComponentInternalRowId"
:row="row"
:columns="columns"
@rowClick="emitRowClick"
/>
</tbody>
<tfoot>
<slot :rows="rows" name="tfoot" />
</tfoot>
</table>
</div>
<div v-if="displayedRows.length === 0 && !loading" class="table-component__message">{{ filterNoResults }}</div>
<div style="display:none;">
<slot />
</div>
<pagination v-if="pagination && !loading" :pagination="pagination" @pageChange="pageChange" />
</div>
</template>
<script>
import Column from '../classes/Column'
import expiringStorage from '../expiring-storage'
import Row from '../classes/Row'
import TableColumnHeader from './TableColumnHeader'
import TableRow from './TableRow'
import settings from '../settings'
import Pagination from './Pagination'
import { classList, pick } from '../helpers'
export default {
components: {
TableColumnHeader,
TableRow,
Pagination
},
props: {
data: { default: () => [], type: [Array, Function] },
showFilter: { type: Boolean, default: true },
showCaption: { type: Boolean, default: true },
sortBy: { default: '', type: String },
sortOrder: { default: '', type: String },
cacheKey: { default: null },
cacheLifetime: { default: 5 },
tableClass: { default: () => settings.tableClass },
theadClass: { default: () => settings.theadClass },
tbodyClass: { default: () => settings.tbodyClass },
filterInputClass: { default: () => settings.filterInputClass },
filterPlaceholder: { default: () => settings.filterPlaceholder },
filterNoResults: { default: () => settings.filterNoResults }
},
data: () => ({
columns: [],
rows: [],
filter: '',
sort: {
fieldName: '',
order: ''
},
pagination: null,
loading: false,
localSettings: {}
}),
computed: {
fullTableClass () {
return classList('table-component__table', this.tableClass)
},
fullTableHeadClass () {
return classList('table-component__table__head', this.theadClass)
},
fullTableBodyClass () {
return classList('table-component__table__body', this.tbodyClass)
},
fullFilterInputClass () {
return classList('table-component__filter__field', this.filterInputClass)
},
ariaCaption () {
if (this.sort.fieldName === '') {
return 'Table not sorted'
}
return (
`Table sorted by ${this.sort.fieldName} ` +
(this.sort.order === 'asc' ? '(ascending)' : '(descending)')
)
},
usesLocalData () {
return Array.isArray(this.data)
},
displayedRows () {
if (!this.usesLocalData) {
return this.sortedRows
}
if (!this.showFilter) {
return this.sortedRows
}
if (!this.columns.filter(column => column.isFilterable()).length) {
return this.sortedRows
}
return this.sortedRows.filter(row => row.passesFilter(this.filter))
},
sortedRows () {
if (!this.usesLocalData) {
return this.rows
}
if (this.sort.fieldName === '') {
return this.rows
}
if (this.columns.length === 0) {
return this.rows
}
const sortColumn = this.getColumn(this.sort.fieldName)
if (!sortColumn) {
return this.rows
}
return this.rows.sort(
sortColumn.getSortPredicate(this.sort.order, this.columns)
)
},
filterableColumnExists () {
return this.columns.filter(c => c.isFilterable()).length > 0
},
storageKey () {
return this.cacheKey
? `vue-table-component.${this.cacheKey}`
: `vue-table-component.${window.location.host}${window.location.pathname}${this.cacheKey}`
}
},
watch: {
filter () {
if (!this.usesLocalData) {
this.mapDataToRows()
}
this.saveState()
},
data () {
if (this.usesLocalData) {
this.mapDataToRows()
}
}
},
created () {
this.sort.order = this.sortOrder
this.restoreState()
},
async mounted () {
this.sort.fieldName = this.sortBy
const columnComponents = this.$slots.default
.filter(column => column.componentInstance)
.map(column => column.componentInstance)
this.columns = columnComponents.map(column => new Column(column))
columnComponents.forEach(columnCom => {
Object.keys(columnCom.$options.props).forEach(prop =>
columnCom.$watch(prop, () => {
this.columns = columnComponents.map(column => new Column(column))
})
)
})
await this.mapDataToRows()
},
methods: {
async pageChange (page) {
this.pagination.currentPage = page
await this.mapDataToRows()
},
async mapDataToRows () {
const data = this.usesLocalData
? this.prepareLocalData()
: await this.fetchServerData()
let rowId = 0
this.rows = data
.map(rowData => {
rowData.vueTableComponentInternalRowId = rowId++
return rowData
})
.map(rowData => new Row(rowData, this.columns))
},
prepareLocalData () {
this.pagination = null
return this.data
},
async fetchServerData () {
const page = (this.pagination && this.pagination.currentPage) || 1
this.loading = true
const response = await this.data({
filter: this.filter,
sort: this.sort,
page: page
})
this.pagination = response.pagination
this.loading = false
return response.data
},
async refresh () {
if (this.pagination) {
this.pagination.currentPage = 1
}
await this.mapDataToRows()
},
changeSorting (column) {
if (this.sort.fieldName !== (column.sortAs || column.show)) {
this.sort.fieldName = (column.sortAs || column.show)
this.sort.order = 'asc'
} else {
this.sort.order = this.sort.order === 'asc' ? 'desc' : 'asc'
}
if (!this.usesLocalData) {
this.mapDataToRows()
}
this.saveState()
},
getColumn (columnName) {
return this.columns.find(column => column.show === columnName)
},
saveState () {
expiringStorage.set(
this.storageKey,
pick(this.$data, ['filter', 'sort']),
this.cacheLifetime
)
},
restoreState () {
const previousState = expiringStorage.get(this.storageKey)
if (previousState === null) {
return
}
this.sort = previousState.sort
this.filter = previousState.filter
this.saveState()
},
emitRowClick (row) {
this.$emit('rowClick', row)
this.$emit('row-click', row)
}
}
}
</script>

View File

@ -1,38 +0,0 @@
<template>
<tr @click="onClick">
<table-cell
v-for="column in visibleColumns"
:row="row"
:column="column"
:key="column.id"
:responsive-label="column.label"
></table-cell>
</tr>
</template>
<script>
import TableCell from './TableCell';
export default {
props: ['columns', 'row'],
components: {
TableCell,
},
computed: {
visibleColumns() {
return this.columns.filter(column => ! column.hidden);
}
},
methods: {
onClick(e) {
this.$emit('rowClick', {
e,
row: this.row
})
}
}
};
</script>

View File

@ -1,34 +0,0 @@
class ExpiringStorage {
get (key) {
const cached = JSON.parse(
localStorage.getItem(key)
)
if (!cached) {
return null
}
const expires = new Date(cached.expires)
if (expires < new Date()) {
localStorage.removeItem(key)
return null
}
return cached.value
}
has (key) {
return this.get(key) !== null
}
set (key, value, lifeTimeInMinutes) {
const currentTime = new Date().getTime()
const expires = new Date(currentTime + lifeTimeInMinutes * 60000)
localStorage.setItem(key, JSON.stringify({ value, expires }))
}
}
export default new ExpiringStorage()

View File

@ -1,30 +0,0 @@
export function classList (...classes) {
return classes
.map(c => Array.isArray(c) ? c : [c])
.reduce((classes, c) => classes.concat(c), [])
}
export function get (object, path) {
if (!path) {
return object
}
if (object === null || typeof object !== 'object') {
return object
}
const [pathHead, pathTail] = path.split(/\.(.+)/)
return get(object[pathHead], pathTail)
}
export function pick (object, properties) {
return properties.reduce((pickedObject, property) => {
pickedObject[property] = object[property]
return pickedObject
}, {})
}
export function range (from, to) {
return [...Array(to - from)].map((_, i) => i + from)
}

View File

@ -1,20 +0,0 @@
import TableComponent from './components/TableComponent'
import TableColumn from './components/TableColumn'
import Pagination from './components/Pagination'
import { mergeSettings } from './settings'
export default {
install (Vue, options = {}) {
mergeSettings(options)
Vue.component('table-component', TableComponent)
Vue.component('table-column', TableColumn)
Vue.component('pagination', Pagination)
},
settings (settings) {
mergeSettings(settings)
}
}
export { TableComponent, TableColumn }

View File

@ -1,18 +0,0 @@
const settings = {
tableClass: '',
theadClass: '',
tbodyClass: '',
headerClass: '',
cellClass: '',
filterInputClass: '',
filterPlaceholder: 'Filter table…',
filterNoResults: 'There are no matching rows'
}
export function mergeSettings (newSettings) {
for (const setting in newSettings) {
settings[setting] = newSettings[setting]
}
}
export default settings

View File

@ -1,37 +1,42 @@
import BaseButton from './BaseButton.vue'
import ItemModal from './modal/ItemModal.vue'
import BaseModal from './modal/BaseModal.vue'
import BaseDatePicker from './base-date-picker/BaseDatePicker.vue'
import BaseInput from './BaseInput.vue'
import BaseSwitch from './BaseSwitch.vue'
import BaseTextArea from './BaseTextArea.vue'
import BaseSelect from './base-select/BaseSelect.vue'
import BaseLoader from './BaseLoader.vue'
import BaseCustomerSelect from './BaseCustomerSelect.vue'
import BasePrefixInput from './BasePrefixInput.vue'
import BasePopup from './popup/BasePopup.vue'
import BaseCustomInput from './BaseCustomInput.vue'
import CustomerSelectPopup from './popup/CustomerSelectPopup.vue'
import TaxSelectPopup from './popup/TaxSelectPopup.vue'
import NoteSelectPopup from './popup/NoteSelectPopup.vue'
import {TableColumn, TableComponent} from './base-table/index'
import BaseDatePicker from '../base/BaseDatePicker.vue'
import BaseTimePicker from './BaseTimePicker.vue'
import BasePage from './BasePage.vue'
import GlobalSearch from '../GlobalSearch.vue'
import DotIcon from '../../components/icon/DotIcon.vue'
import SaveIcon from '../../components/icon/SaveIcon.vue'
import SwSelect from '@bytefury/spacewind/src/components/sw-select'
Vue.component('base-button', BaseButton)
Vue.component('item-modal', ItemModal)
Vue.component('base-modal', BaseModal)
Vue.component('global-search', GlobalSearch)
Vue.component('base-page', BasePage)
Vue.component('base-date-picker', BaseDatePicker)
Vue.component('base-input', BaseInput)
Vue.component('base-switch', BaseSwitch)
Vue.component('base-text-area', BaseTextArea)
Vue.component('base-loader', BaseLoader)
Vue.component('base-prefix-input', BasePrefixInput)
Vue.component('sw-select', SwSelect)
Vue.component('table-component', TableComponent)
Vue.component('table-column', TableColumn)
Vue.component('base-select', BaseSelect)
Vue.component('base-customer-select', BaseCustomerSelect)
Vue.component('base-custom-input', BaseCustomInput)
Vue.component('base-popup', BasePopup)
Vue.component('customer-select-popup', CustomerSelectPopup)
Vue.component('tax-select-popup', TaxSelectPopup)
Vue.component('note-select-popup', NoteSelectPopup)
Vue.component('base-time-picker', BaseTimePicker)
Vue.component('dot-icon', DotIcon)
Vue.component('save-icon', SaveIcon)

View File

@ -0,0 +1,179 @@
<template>
<div class="relative customer-modal">
<base-loader
v-if="isRequestOngoing"
class="h-130"
:show-bg-overlay="true"
/>
<form @submit.prevent="createNewBackup">
<div class="p-6">
<sw-input-group
:label="$t('settings.backup.select_backup_type')"
:error="optionError"
horizontal
required
class="py-2"
>
<sw-select
v-model="formData.option"
:options="options"
:searchable="true"
:show-labels="false"
:placeholder="$t('settings.backup.select_backup_type')"
:allow-empty="false"
:maxHeight="100"
/>
</sw-input-group>
<sw-input-group
:label="$t('settings.disk.select_disk')"
:error="selectDiskError"
horizontal
required
class="py-2"
>
<sw-select
v-model="formData.selected_disk"
:options="getDisks"
:searchable="true"
:show-labels="false"
:placeholder="$t('settings.disk.select_disk')"
:allow-empty="false"
track-by="id"
:preselect-first="true"
:custom-label="getCustomLabel"
:maxHeight="100"
:loading="isLoading"
/>
</sw-input-group>
</div>
<div
class="z-0 flex justify-end p-4 border-t border-gray-200 border-solid"
>
<sw-button
class="mr-3"
variant="primary-outline"
type="button"
@click="cancelBackup"
>
{{ $t('general.cancel') }}
</sw-button>
<sw-button
:loading="isCreateLoading"
variant="primary"
type="submit"
:disabled="isCreateLoading"
>
<save-icon v-if="!isCreateLoading" class="mr-2" />
{{ $t('general.create') }}
</sw-button>
</div>
</form>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import AddressStub from '../../../stub/address'
import _ from 'lodash'
const { required } = require('vuelidate/lib/validators')
export default {
data() {
return {
isLoading: false,
isCreateLoading: false,
isRequestOngoing: false,
formData: {
option: 'full',
selected_disk: { driver: 'local' },
},
options: ['full', 'only-db', 'only-files'],
}
},
validations: {
formData: {
option: {
required,
},
selected_disk: {
required,
},
},
},
computed: {
...mapGetters('disks', ['getDisks']),
...mapGetters('modal', ['refreshData']),
optionError() {
if (!this.$v.formData.option.$error) {
return ''
}
if (!this.$v.formData.option) {
return this.$tc('validation.required')
}
},
selectDiskError() {
if (!this.$v.formData.selected_disk.$error) {
return ''
}
if (!this.$v.formData.selected_disk) {
return this.$tc('validation.required')
}
},
},
created() {
this.loadData()
},
methods: {
...mapActions('backup', ['createBackup']),
...mapActions('disks', ['fetchDisks']),
...mapActions('modal', ['closeModal']),
getCustomLabel({ driver, name }) {
return `${name} — [${driver}]`
},
async createNewBackup() {
let data = {
option: this.formData.option,
file_disk_id: this.formData.selected_disk.id,
}
try {
this.isCreateLoading = true
await this.createBackup(data)
this.isCreateLoading = false
window.toastr['success'](this.$t('settings.backup.created_message'))
this.refreshData ? this.refreshData() : ''
this.cancelBackup()
} catch (e) {
this.isCreateLoading = false
window.toastr['error'](e.response.data.message)
}
},
async loadData() {
this.isRequestOngoing = true
let res = await this.fetchDisks({ limit: 'all' })
this.formData.selected_disk = res.data.disks.data[0]
this.isRequestOngoing = false
},
cancelBackup() {
this.closeModal()
},
},
}
</script>

View File

@ -1,19 +1,22 @@
<template>
<transition name="fade">
<div v-if="modalActive" :class="'size-' + modalSize" class="base-modal">
<div class="modal-body">
<div class="close-icon" @click="closeModal">
<font-awesome-icon icon="times" />
<div>
<sw-modal ref="baseModal" :variant="variant">
<template v-slot:header>
<div
class="absolute flex content-center justify-center w-5 cursor-pointer"
style="top: 20px; right: 15px"
@click="closeModal"
>
<x-icon />
</div>
<div class="modal-header p-3">
<h5 class="modal-heading">{{ modalTitle }}</h5>
</div>
<component :is="component" />
</div>
</div>
</transition>
<span>{{ modalTitle }}</span>
</template>
<component :is="component" />
</sw-modal>
</div>
</template>
<script>
import { XIcon } from '@vue-hero-icons/solid'
import { mapActions, mapGetters } from 'vuex'
import TaxTypeModal from './TaxTypeModal'
import ItemModal from './ItemModal'
@ -21,9 +24,17 @@ import EstimateTemplate from './EstimateTemplate'
import InvoiceTemplate from './InvoiceTemplate'
import CustomerModal from './CustomerModal'
import CategoryModal from './CategoryModal'
import BackupModal from './BackupModal'
import PaymentMode from './PaymentModeModal'
import ItemUnit from './ItemUnitModal'
import MailTestModal from './MailTestModal'
import SendInvoiceModal from './SendInvoiceModal'
import SendEstimateModal from './SendEstimateModal'
import SendPaymentModal from './SendPaymentModal'
import FileDiskModal from './FileDiskModal'
import SetDefaultDiskModal from './SetDefaultDiskModal'
import CustomFieldModal from './CustomField/Index'
import NoteSelectModal from './NoteModal'
export default {
components: {
@ -33,14 +44,22 @@ export default {
InvoiceTemplate,
CustomerModal,
CategoryModal,
BackupModal,
PaymentMode,
ItemUnit,
MailTestModal
MailTestModal,
SendInvoiceModal,
SendEstimateModal,
SendPaymentModal,
XIcon,
FileDiskModal,
SetDefaultDiskModal,
CustomFieldModal,
NoteSelectModal,
},
data () {
data() {
return {
component: '',
hasFocus: false
}
},
computed: {
@ -49,31 +68,28 @@ export default {
'modalTitle',
'componentName',
'modalSize',
'modalData'
])
'modalData',
'variant',
]),
},
watch: {
componentName (component) {
componentName(component) {
if (!component) {
return
}
this.component = component
}
},
modalActive(status) {
if (status) {
this.$refs.baseModal.open()
return true
}
this.$refs.baseModal.close()
return false
},
},
methods: {
...mapActions('modal', [
'openModal',
'closeModal'
])
}
...mapActions('modal', ['openModal', 'closeModal']),
},
}
</script>
<style>
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
</style>

View File

@ -1,98 +1,115 @@
<template>
<div class="category-modal">
<form action="" @submit.prevent="submitCategoryData">
<div class="card-body">
<div class="form-group row">
<label class="col-sm-4 col-form-label input-label">{{ $t('expenses.category') }}<span class="required text-danger">*</span></label>
<div class="col-sm-7">
<base-input
ref="name"
:invalid="$v.formData.name.$error"
v-model="formData.name"
type="text"
@input="$v.formData.name.$touch()"
/>
<form action="" @submit.prevent="submitCategoryData">
<div class="p-8 sm:p-6">
<sw-input-group
:label="$t('expenses.category')"
:error="nameError"
variant="horizontal"
required
>
<sw-input
ref="name"
:invalid="$v.formData.name.$error"
v-model="formData.name"
type="text"
@input="$v.formData.name.$touch()"
/>
</sw-input-group>
<div v-if="$v.formData.name.$error">
<span v-if="!$v.formData.name.required" class="text-danger">{{ $tc('validation.required') }}</span>
<span v-if="!$v.formData.name.minLength" class="text-danger"> {{ $tc('validation.name_min_length', $v.formData.name.$params.minLength.min, { count: $v.formData.name.$params.minLength.min }) }} </span>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label input-label">{{ $t('expenses.description') }}</label>
<div class="col-sm-7">
<base-text-area
v-model="formData.description"
rows="4"
cols="50"
@input="$v.formData.description.$touch()"
/>
<div v-if="$v.formData.description.$error">
<span v-if="!$v.formData.name.maxLength" class="text-danger"> {{ $tc('validation.description_maxlength') }} </span>
</div>
</div>
</div>
</div>
<div class="card-footer">
<base-button
:outline="true"
class="mr-3"
color="theme"
@click="closeCategoryModal"
>
{{ $t('general.cancel') }}
</base-button>
<base-button
:loading="isLoading"
icon="save"
color="theme"
type="submit"
>
{{ !isEdit ? $t('general.save') : $t('general.update') }}
</base-button>
</div>
</form>
</div>
<sw-input-group
:label="$t('expenses.description')"
:error="descriptionError"
class="mt-5"
variant="horizontal"
>
<sw-textarea
v-model="formData.description"
rows="4"
cols="50"
@input="$v.formData.description.$touch()"
/>
</sw-input-group>
</div>
<div
class="z-0 flex justify-end p-4 border-t border-gray-200 border-solid border-modal-bg"
>
<sw-button
type="button"
variant="primary-outline"
class="mr-3 text-sm"
@click="closeCategoryModal"
>
{{ $t('general.cancel') }}
</sw-button>
<sw-button variant="primary" type="submit" :loading="isLoading">
<save-icon v-if="!isLoading" class="mr-2" />
{{ !isEdit ? $t('general.save') : $t('general.update') }}
</sw-button>
</div>
</form>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import { validationMixin } from 'vuelidate'
const { required, minLength, maxLength } = require('vuelidate/lib/validators')
export default {
mixins: [validationMixin],
data () {
data() {
return {
isEdit: false,
isLoading: false,
formData: {
id: null,
name: null,
description: null
}
description: null,
},
}
},
computed: {
...mapGetters('modal', [
'modalDataID',
'modalData',
'modalActive'
])
'modalActive',
'refreshData',
]),
nameError() {
if (!this.$v.formData.name.$error) {
return ''
}
if (!this.$v.formData.name.required) {
return this.$tc('validation.required')
}
if (!this.$v.formData.name.minLength) {
return this.$tc(
'validation.name_min_length',
this.$v.formData.name.$params.minLength.min,
{ count: this.$v.formData.name.$params.minLength.min }
)
}
},
descriptionError() {
if (!this.$v.formData.description.$error) {
return ''
}
if (!this.$v.formData.name.maxLength) {
return this.$tc('validation.description_maxlength')
}
},
},
validations: {
formData: {
name: {
required,
minLength: minLength(3)
minLength: minLength(3),
},
description: {
maxLength: maxLength(255)
}
}
maxLength: maxLength(255),
},
},
},
watch: {
'modalDataID' (val) {
modalDataID(val) {
if (val) {
this.isEdit = true
this.setData()
@ -100,40 +117,33 @@ export default {
this.isEdit = false
}
},
'modalActive' (val) {
modalActive(val) {
if (!this.modalActive) {
this.resetFormData()
}
}
},
},
mounted () {
mounted() {
this.$refs.name.focus = true
if (this.modalDataID) {
this.isEdit = true
this.setData()
}
},
destroyed () {
},
methods: {
...mapActions('modal', [
'closeModal',
'resetModalData'
]),
...mapActions('category', [
'addCategory',
'updateCategory'
]),
resetFormData () {
...mapActions('modal', ['closeModal']),
...mapActions('category', ['addCategory', 'updateCategory']),
resetFormData() {
this.formData = {
id: null,
name: null,
description: null
description: null,
}
this.$v.formData.$reset()
},
async submitCategoryData () {
async submitCategoryData() {
this.$v.formData.$touch()
if (this.$v.$invalid) {
@ -149,28 +159,33 @@ export default {
if (response.data) {
if (!this.isEdit) {
window.toastr['success'](this.$t('settings.expense_category.created_message'))
window.toastr['success'](
this.$t('settings.expense_category.created_message')
)
} else {
window.toastr['success'](this.$t('settings.expense_category.updated_message'))
window.toastr['success'](
this.$t('settings.expense_category.updated_message')
)
}
window.hub.$emit('newCategory', response.data.category)
this.refreshData ? this.refreshData() : ''
this.closeCategoryModal()
this.isLoading = false
return true
}
window.toastr['error'](response.data.error)
},
async setData () {
async setData() {
this.formData = {
id: this.modalData.id,
name: this.modalData.name,
description: this.modalData.description
description: this.modalData.description,
}
},
closeCategoryModal () {
closeCategoryModal() {
this.resetFormData()
this.closeModal()
}
}
},
},
}
</script>

View File

@ -0,0 +1,500 @@
<template>
<div class="custom-field-modal">
<form action="" @submit.prevent="submitCustomFieldData">
<div
class="px-8 py-8 overflow-y-auto sw-scroll sm:p-6"
style="max-height: 600px"
>
<sw-input-group
:label="$t('settings.custom_fields.name')"
:error="nameError"
horizontal
required
>
<sw-input
ref="name"
:invalid="$v.formData.name.$error"
v-model="formData.name"
type="text"
@input="$v.formData.name.$touch()"
/>
</sw-input-group>
<sw-input-group
:label="$t('settings.custom_fields.model')"
:error="modalTypeError"
class="mt-5"
horizontal
required
>
<sw-select
v-model="formData.model_type"
:options="modelTypes"
:invalid="$v.formData.model_type.$error"
:searchable="true"
:show-labels="false"
:allow-empty="false"
@input="$v.formData.model_type.$touch()"
/>
</sw-input-group>
<sw-input-group
:label="$t('settings.custom_fields.required')"
class="mt-5"
horizontal
>
<sw-switch v-model="formData.is_required" style="margin-top: -20px" />
</sw-input-group>
<sw-input-group
:label="$t('settings.custom_fields.type')"
:error="dataTypeError"
class="mt-5"
horizontal
required
>
<sw-select
v-model="selectType"
:options="dataTypes"
:invalid="$v.selectType.$error"
:searchable="true"
:show-labels="false"
:allow-empty="false"
track-by="label"
label="label"
@input="onSelectTypeChange"
/>
</sw-input-group>
<sw-input-group
:label="$t('settings.custom_fields.label')"
:error="labelError"
class="mt-5"
horizontal
required
>
<sw-input
ref="name"
:invalid="$v.formData.label.$error"
v-model="formData.label"
type="text"
@input="$v.formData.label.$touch()"
/>
</sw-input-group>
<sw-input-group
:label="$t('settings.custom_fields.options')"
class="mt-5"
v-if="isDropdownSelected"
horizontal
>
<option-create @onAdd="addNewOptions" />
<div
v-for="(option, index) in formData.options"
:key="index"
class="flex items-center"
style="margin-top: 5px"
>
<sw-input v-model="option.name" type="text" style="width: 90%" />
<minus-circle-icon
@click="removeOption(index)"
class="ml-1 cursor-pointer icon text-danger"
/>
</div>
</sw-input-group>
<sw-input-group
:label="$t('settings.custom_fields.default_value')"
horizontal
class="relative mt-5"
v-if="formData.type"
>
<component
:value="formData.default_answer"
:is="formData.type + 'Type'"
:options="formData.options"
:defaultDateTime="formData.dateTimeValue"
v-model="formData.default_answer"
/>
</sw-input-group>
<sw-input-group
:label="$t('settings.custom_fields.placeholder')"
v-if="!isSwitchTypeSelected"
class="mt-5"
horizontal
>
<sw-input v-model="formData.placeholder" type="text" />
</sw-input-group>
<sw-input-group
:label="$t('settings.custom_fields.order')"
:error="orderError"
class="mt-5"
horizontal
>
<sw-input
v-model="formData.order"
:invalid="$v.formData.order.$error"
type="number"
@input="$v.formData.order.$touch()"
/>
</sw-input-group>
</div>
<div
class="z-0 flex justify-end p-4 border-t border-solid border-gray-light border-modal-bg"
>
<sw-button
class="mr-3"
type="button"
variant="primary-outline"
@click="closeCategoryModal"
>
{{ $t('general.cancel') }}
</sw-button>
<sw-button
:loading="isLoading"
:disabled="isLoading"
variant="primary"
type="submit"
>
<save-icon v-if="!isLoading" class="mr-2" />
{{ !isEdit ? $t('general.save') : $t('general.update') }}
</sw-button>
</div>
</form>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import { MinusCircleIcon } from '@vue-hero-icons/solid'
import InputType from './types/InputType'
import NumberType from './types/NumberType'
import SwitchType from './types/SwitchType'
import TextAreaType from './types/TextAreaType'
import TimeType from './types/TimeType'
import UrlType from './types/UrlType'
import PhoneType from './types/PhoneType'
import DateTimeType from './types/DateTimeType'
import DateType from './types/DateType'
import DropdownType from './types/DropdownType'
import OptionCreate from './types/OptionsCreate'
import moment from 'moment'
const {
required,
requiredIf,
numeric,
minLength,
} = require('vuelidate/lib/validators')
export default {
components: {
MinusCircleIcon,
OptionCreate,
InputType,
NumberType,
SwitchType,
TextAreaType,
TimeType,
UrlType,
PhoneType,
DateTimeType,
DateType,
DropdownType,
},
data() {
return {
isEdit: false,
dateType: 'custom',
isLoading: false,
relativeDateTypes: [
'Today',
'Tomorrow',
'Yesterday',
'Starting Date of Week',
'Ending Date of Week',
'Starting Date of Next Week',
'Ending Date of Next Week',
'Starting Date of Previous Week',
'Ending Date of Previous Week',
'Starting Date of Month',
'Ending Date of Month',
'Starting Date of Next Month',
'Ending Date of Next Month',
'Starting Date of Previous Month',
'Ending Date of Previous Month',
'Starting Date of Fiscal Month',
'Ending Date of Fiscal Month',
],
dataTypes: [
{ label: 'Text', value: 'Input' },
{ label: 'Textarea', value: 'TextArea' },
{ label: 'Phone', value: 'Phone' },
{ label: 'URL', value: 'Url' },
{ label: 'Number', value: 'Number' },
{ label: 'Select Field', value: 'Dropdown' },
{ label: 'Switch Toggle', value: 'Switch' },
{ label: 'Date', value: 'Date' },
{ label: 'Time', value: 'Time' },
{ label: 'Date & Time', value: 'DateTime' },
],
modelTypes: ['Customer', 'Invoice', 'Estimate', 'Expense', 'Payment'],
selectType: null,
formData: {
label: null,
type: null,
name: null,
default_answer: null,
is_required: false,
placeholder: null,
model_type: null,
order: 1,
options: [],
},
}
},
validations: {
selectType: {
required,
},
formData: {
name: {
required,
},
label: {
required,
},
model_type: {
required,
},
order: {
required,
numeric,
},
options: {
required: requiredIf('isDropdownSelected'),
minLength: minLength(1),
$each: {
name: {
required: requiredIf('isDropdownSelected'),
minLength: minLength(1),
},
},
},
},
},
computed: {
...mapGetters('modal', ['modalData', 'modalDataID', 'refreshData']),
isDropdownSelected() {
if (this.selectType && this.selectType.label === 'Select Field') {
return true
}
return false
},
isSwitchTypeSelected() {
if (this.selectType && this.selectType.label === 'Switch Toggle') {
return true
}
return false
},
nameError() {
if (!this.$v.formData.name.$error) {
return ''
}
if (!this.$v.formData.name.required) {
return this.$tc('validation.required')
}
},
labelError() {
if (!this.$v.formData.label.$error) {
return ''
}
if (!this.$v.formData.label.required) {
return this.$tc('validation.required')
}
},
modalTypeError() {
if (!this.$v.formData.model_type.$error) {
return ''
}
if (!this.$v.formData.model_type.required) {
return this.$tc('validation.required')
}
},
dataTypeError() {
if (!this.$v.selectType.$error) {
return ''
}
if (!this.$v.selectType.required) {
return this.$tc('validation.required')
}
},
hasPlaceHolder() {
if (this.selectType.label == 'Switch Toggle') {
return false
}
return true
},
orderError() {
if (!this.$v.formData.order.$error) {
return ''
}
if (!this.$v.formData.order.required) {
return this.$tc('validation.required')
}
if (!this.$v.formData.order.numeric) {
return this.$tc('validation.numbers_only')
}
},
},
watch: {
'formData.type'(newValue, oldvalue) {
if (oldvalue != null || oldvalue != undefined) {
this.onChangeReset()
}
},
dateType(newValue, oldvalue) {
if (oldvalue != null || oldvalue != undefined) {
this.onChangeReset()
}
},
},
mounted() {
if (this.modalDataID) {
this.setData()
return true
}
this.formData.model_type = this.modelTypes[0]
this.selectType = this.dataTypes[0]
this.formData.type = this.dataTypes[0].value
},
methods: {
...mapActions('customFields', [
'addCustomField',
'updateCustomField',
'fetchCustomField',
]),
...mapActions('modal', ['closeModal']),
resetFormData() {
this.formData = {
label: null,
label: null,
type: null,
dateTimeValue: null,
default_answer: null,
is_required: false,
placeholder: null,
model_type: null,
options: [{ name: '' }],
}
this.$v.$reset()
},
async submitCustomFieldData() {
this.$v.selectType.$touch()
this.$v.formData.$touch()
if (this.$v.$invalid) {
return false
}
let data = {
...this.formData,
options: this.formData.options.map((option) => option.name),
default_answer:
this.isDropdownSelected && this.formData.default_answer
? this.formData.default_answer.name
: this.formData.default_answer,
}
if (this.isSwitchTypeSelected && this.formData.default_answer == null) {
data.default_answer = false
}
if (data.type == 'Date') {
data.default_answer = data.default_answer
? moment(data.default_answer).format('YYYY-MM-DD')
: null
}
if (data.type == 'Time' && typeof data.default_answer == 'object') {
let HH =
data && data.default_answer && data.default_answer.HH
? data.default_answer.HH
: null
let mm =
data && data.default_answer && data.default_answer.mm
? data.default_answer.mm
: null
let ss =
data && data.default_answer && data.default_answer.ss
? data.default_answer.ss
: null
data.default_answer = `${HH}:${mm}:${ss}`
}
let response = null
if (this.isEdit) {
this.isLoading = true
response = await this.updateCustomField(data)
window.toastr['success'](
this.$tc('settings.custom_fields.updated_message')
)
this.refreshData()
this.closeCategoryModal()
return true
}
this.isLoading = true
response = await this.addCustomField(data)
window.toastr['success'](this.$tc('settings.custom_fields.added_message'))
this.refreshData()
this.closeCategoryModal()
return true
},
addNewOptions(option) {
this.formData.options = [{ name: option }, ...this.formData.options]
},
removeOption(index) {
this.formData.options.splice(index, 1)
},
async setData() {
let response = await this.fetchCustomField(this.modalDataID)
let fieldData = response.data.customField
this.isEdit = true
let data = {
...fieldData,
options: [],
dateTimeValue: fieldData.defaultAnswer,
default_answer: fieldData.defaultAnswer,
options: fieldData.options
? fieldData.options.map((option) => {
return { name: option ? option : '' }
})
: [],
}
this.selectType = this.dataTypes.find(
(type) => type.value == fieldData.type
)
if (data.type == 'Dropdown') {
data.default_answer = { name: fieldData.defaultAnswer }
}
this.formData = { ...data }
},
onChangeReset() {
this.formData = {
...this.formData,
default_answer: null,
is_required: false,
placeholder: null,
options: [],
}
},
onSelectTypeChange(data) {
this.formData.type = data.value
this.$v.selectType.$touch()
},
closeCategoryModal() {
this.resetFormData()
this.closeModal()
},
},
}
</script>

View File

@ -0,0 +1,35 @@
<template>
<base-date-picker
v-model="inputValue"
:enable-time="true"
@input="$emit('input', inputValue)"
/>
</template>
<script>
import moment from 'moment'
export default {
props: {
value: {
type: String,
default: null,
},
defaultDateTime: {
type: String,
default: null,
},
},
data() {
return {
inputValue: this.value ? this.value : moment().format('YYYY-MM-DD hh:mm'),
}
},
watch: {
value(data) {
this.inputValue = data
},
defaultDateTime(data) {
this.dateTimeValue = data
},
},
}
</script>

View File

@ -0,0 +1,29 @@
<template>
<base-date-picker
:calendar-button="true"
v-model="inputValue"
calendar-button-icon="calendar"
@input="$emit('input', inputValue)"
/>
</template>
<script>
import moment from 'moment'
export default {
props: {
value: {
type: [String, Date],
default: null,
},
},
data() {
return {
inputValue: moment().format('YYYY-MM-DD'),
}
},
watch: {
value(data) {
this.inputValue = data
},
},
}
</script>

View File

@ -0,0 +1,37 @@
<template>
<sw-select
v-model="inputValue"
:options="inputOptions"
:taggable="true"
:show-labels="false"
label="name"
track-by="name"
@input="$emit('input', inputValue)"
/>
</template>
<script>
export default {
props: {
value: {
type: [String, Object],
default: null,
},
options: {
type: Array,
default: () => [],
},
},
data() {
return {
inputValue: this.value,
inputOptions: this.options,
}
},
watch: {
options(data) {
this.inputOptions = data
this.inputValue = null
},
},
}
</script>

View File

@ -0,0 +1,27 @@
<template>
<sw-input
v-model="inputValue"
type="text"
@input="$emit('input', inputValue)"
/>
</template>
<script>
export default {
props: {
value: {
type: String,
default: null,
},
},
data() {
return {
inputValue: this.value,
}
},
watch: {
value(data) {
this.inputValue = data
},
},
}
</script>

View File

@ -0,0 +1,27 @@
<template>
<sw-input
v-model="inputValue"
type="number"
@input="$emit('input', inputValue)"
/>
</template>
<script>
export default {
props: {
value: {
type: [String, Number],
default: null,
},
},
data() {
return {
inputValue: this.value,
}
},
watch: {
value(data) {
this.inputValue = data
},
},
}
</script>

View File

@ -0,0 +1,41 @@
<template>
<div class="flex items-center" style="margin-top: 5px">
<sw-input
v-model="option"
type="text"
style="width: 90%"
@handleEnter.stop="onAddOption"
/>
<plus-circle-icon
class="ml-1 cursor-pointer text-danger"
@click="onAddOption"
/>
</div>
</template>
<script>
import { PlusCircleIcon } from '@vue-hero-icons/solid'
export default {
components: {
PlusCircleIcon,
},
data() {
return {
option: null,
}
},
methods: {
onAddOption() {
if (
this.option == null ||
this.option == '' ||
this.option == undefined
) {
return true
}
this.$emit('onAdd', this.option)
this.option = null
},
},
}
</script>

View File

@ -0,0 +1,27 @@
<template>
<sw-input
v-model="inputValue"
type="text"
@input="$emit('input', inputValue)"
/>
</template>
<script>
export default {
props: {
value: {
type: [String, Number],
default: null,
},
},
data() {
return {
inputValue: this.value,
}
},
watch: {
value(data) {
this.inputValue = data
},
},
}
</script>

View File

@ -0,0 +1,27 @@
<template>
<sw-switch
class="-mt-3"
v-model="inputValue"
@input="$emit('input', inputValue)"
/>
</template>
<script>
export default {
props: {
value: {
type: [Boolean, Number],
default: false,
},
},
data() {
return {
inputValue: this.value,
}
},
watch: {
value(data) {
this.inputValue = data
},
},
}
</script>

View File

@ -0,0 +1,28 @@
<template>
<sw-textarea
v-model="inputValue"
rows="2"
name="description"
@input="$emit('input', inputValue)"
/>
</template>
<script>
export default {
props: {
value: {
type: String,
default: null,
},
},
data() {
return {
inputValue: this.value,
}
},
watch: {
value(data) {
this.inputValue = data
},
},
}
</script>

View File

@ -0,0 +1,28 @@
<template>
<base-time-picker
:value="inputValue"
v-model="inputValue"
hide-clear-button
@input="$emit('input', inputValue)"
/>
</template>
<script>
export default {
props: {
value: {
type: [String, Object],
default: null,
},
},
data() {
return {
inputValue: this.value,
}
},
watch: {
value(data) {
this.inputValue = data
},
},
}
</script>

Some files were not shown because too many files have changed in this diff Show More