frappe-ui 0.1.60 → 0.1.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/src/components/Checkbox.vue +6 -3
- package/src/components/DatePicker.story.vue +36 -0
- package/src/components/DatePicker.vue +108 -72
- package/src/components/DateRangePicker.vue +326 -0
- package/src/components/DateTimePicker.vue +405 -0
- package/src/components/FeatherIcon.vue +3 -3
- package/src/components/FileUploader.vue +1 -1
- package/src/components/ListFilter/ListFilter.vue +4 -4
- package/src/components/ListFilter/SearchComplete.vue +2 -2
- package/src/components/ListView/ListHeader.vue +1 -1
- package/src/components/ListView/ListRow.vue +1 -1
- package/src/components/ListView/ListView.vue +2 -2
- package/src/components/Popover.vue +16 -10
- package/src/components/Switch.vue +4 -4
- package/src/components/TextEditor/InsertVideo.vue +2 -2
- package/src/components/TextEditor/TextEditor.vue +1 -1
- package/src/components/TextEditor/image-extension.js +1 -1
- package/src/components/TextEditor/mention.js +1 -1
- package/src/components/Tooltip/Tooltip.vue +1 -1
- package/src/components/toast.js +2 -2
- package/src/fonts/Inter/inter.css +4 -2
- package/src/index.js +2 -0
- package/src/resources/documentResource.js +6 -6
- package/src/resources/listResource.js +7 -7
- package/src/resources/plugin.js +1 -1
- package/src/utils/call.js +1 -1
- package/src/utils/confirmDialog.js +1 -1
- package/src/utils/debounce.ts +1 -1
- package/src/utils/frappeRequest.js +1 -1
- package/src/utils/markdown.js +1 -1
- package/src/utils/pageMeta.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frappe-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.61",
|
|
4
4
|
"description": "A set of components and utilities for rapid UI development",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"@vueuse/core": "^10.4.1",
|
|
51
51
|
"feather-icons": "^4.28.0",
|
|
52
52
|
"idb-keyval": "^6.2.0",
|
|
53
|
+
"prettier": "^3.3.2",
|
|
53
54
|
"radix-vue": "^1.5.3",
|
|
54
55
|
"showdown": "^2.1.0",
|
|
55
56
|
"socket.io-client": "^4.5.1",
|
|
@@ -68,7 +69,7 @@
|
|
|
68
69
|
"husky": ">=6",
|
|
69
70
|
"lint-staged": ">=10",
|
|
70
71
|
"postcss": "^8.4.21",
|
|
71
|
-
"prettier": "
|
|
72
|
+
"prettier": "3.3.2",
|
|
72
73
|
"prettier-plugin-tailwindcss": "^0.1.13",
|
|
73
74
|
"tailwindcss": "^3.2.7",
|
|
74
75
|
"typescript": "^5.0.2",
|
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
:disabled="disabled"
|
|
16
16
|
:id="htmlId"
|
|
17
17
|
:checked="Boolean(modelValue)"
|
|
18
|
-
@change="
|
|
18
|
+
@change="
|
|
19
|
+
(e) =>
|
|
20
|
+
$emit('update:modelValue', (e.target as HTMLInputElement).checked)
|
|
21
|
+
"
|
|
19
22
|
v-bind="attrs"
|
|
20
23
|
/>
|
|
21
24
|
<label class="block" :class="labelClasses" v-if="label" :for="htmlId">
|
|
@@ -64,8 +67,8 @@ const inputClasses = computed(() => {
|
|
|
64
67
|
let interactionClasses = props.disabled
|
|
65
68
|
? ''
|
|
66
69
|
: props.padding
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
? 'focus:ring-0'
|
|
71
|
+
: 'hover:shadow-sm focus:ring-0 focus-visible:ring-2 focus-visible:ring-gray-400 active:bg-gray-100'
|
|
69
72
|
|
|
70
73
|
let sizeClasses = {
|
|
71
74
|
sm: 'w-3.5 h-3.5',
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { reactive, ref } from 'vue'
|
|
3
|
+
import DatePicker from './DatePicker.vue'
|
|
4
|
+
import DateTimePicker from './DateTimePicker.vue'
|
|
5
|
+
import DateRangePicker from './DateRangePicker.vue'
|
|
6
|
+
|
|
7
|
+
const state = reactive({
|
|
8
|
+
variant: 'subtle',
|
|
9
|
+
placeholder: 'Placeholder',
|
|
10
|
+
disabled: false,
|
|
11
|
+
label: 'Label',
|
|
12
|
+
})
|
|
13
|
+
const dateValue = ref('')
|
|
14
|
+
const dateTimeValue = ref('')
|
|
15
|
+
const dateRangeValue = ref('')
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<Story :layout="{ type: 'grid', width: 500 }">
|
|
20
|
+
<Variant title="Date">
|
|
21
|
+
<div class="p-2">
|
|
22
|
+
<DatePicker v-model="dateValue" v-bind="state" />
|
|
23
|
+
</div>
|
|
24
|
+
</Variant>
|
|
25
|
+
<Variant title="Date Time">
|
|
26
|
+
<div class="p-2">
|
|
27
|
+
<DateTimePicker v-model="dateTimeValue" v-bind="state" />
|
|
28
|
+
</div>
|
|
29
|
+
</Variant>
|
|
30
|
+
<Variant title="Date Range">
|
|
31
|
+
<div class="p-2">
|
|
32
|
+
<DateRangePicker v-model="dateRangeValue" v-bind="state" />
|
|
33
|
+
</div>
|
|
34
|
+
</Variant>
|
|
35
|
+
</Story>
|
|
36
|
+
</template>
|
|
@@ -1,91 +1,109 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<Popover
|
|
2
|
+
<Popover
|
|
3
|
+
@open="selectCurrentMonthYear"
|
|
4
|
+
class="flex w-full [&>div:first-child]:w-full"
|
|
5
|
+
>
|
|
3
6
|
<template #target="{ togglePopover }">
|
|
4
7
|
<Input
|
|
8
|
+
readonly
|
|
5
9
|
type="text"
|
|
6
10
|
icon-left="calendar"
|
|
7
|
-
:class="inputClass"
|
|
8
|
-
:value="
|
|
9
|
-
modelValue && formatValue ? formatValue(modelValue) : modelValue || ''
|
|
10
|
-
"
|
|
11
11
|
:placeholder="placeholder"
|
|
12
|
+
:value="dateValue && formatter ? formatter(dateValue) : dateValue"
|
|
12
13
|
@focus="!readonly ? togglePopover() : null"
|
|
13
|
-
|
|
14
|
+
class="w-full"
|
|
15
|
+
:class="inputClass"
|
|
16
|
+
v-bind="$attrs"
|
|
14
17
|
/>
|
|
15
18
|
</template>
|
|
16
|
-
<template #body
|
|
17
|
-
<div
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
<template #body="{ togglePopover }">
|
|
20
|
+
<div
|
|
21
|
+
class="mt-2 w-fit select-none divide-y rounded-lg bg-white text-base shadow-2xl ring-1 ring-black ring-opacity-5 focus:outline-none"
|
|
22
|
+
>
|
|
23
|
+
<div class="flex items-center p-1 text-gray-500">
|
|
24
|
+
<Button variant="ghost" class="h-7 w-7" @click="prevMonth">
|
|
25
|
+
<FeatherIcon
|
|
26
|
+
:stroke-width="2"
|
|
27
|
+
name="chevron-left"
|
|
28
|
+
class="h-4 w-4"
|
|
29
|
+
/>
|
|
30
|
+
</Button>
|
|
31
|
+
<div class="flex-1 text-center text-base font-medium text-gray-700">
|
|
20
32
|
{{ formatMonth }}
|
|
21
|
-
</
|
|
22
|
-
<
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class="h-4 w-4"
|
|
30
|
-
/>
|
|
31
|
-
</div>
|
|
32
|
-
<div
|
|
33
|
-
class="ml-2 grid h-5 w-5 cursor-pointer place-items-center rounded-md hover:bg-gray-100"
|
|
34
|
-
>
|
|
35
|
-
<FeatherIcon
|
|
36
|
-
@click="nextMonth"
|
|
37
|
-
name="chevron-right"
|
|
38
|
-
class="h-4 w-4"
|
|
39
|
-
/>
|
|
40
|
-
</div>
|
|
41
|
-
</span>
|
|
33
|
+
</div>
|
|
34
|
+
<Button variant="ghost" class="h-7 w-7" @click="nextMonth">
|
|
35
|
+
<FeatherIcon
|
|
36
|
+
:stroke-width="2"
|
|
37
|
+
name="chevron-right"
|
|
38
|
+
class="h-4 w-4"
|
|
39
|
+
/>
|
|
40
|
+
</Button>
|
|
42
41
|
</div>
|
|
43
|
-
<div class="
|
|
44
|
-
<
|
|
42
|
+
<div class="flex items-center justify-center gap-1 p-1">
|
|
43
|
+
<TextInput
|
|
44
|
+
class="text-sm"
|
|
45
|
+
type="text"
|
|
46
|
+
:value="dateValue"
|
|
47
|
+
@change="
|
|
48
|
+
selectDate(getDate($event.target.value)) || togglePopover()
|
|
49
|
+
"
|
|
50
|
+
/>
|
|
51
|
+
<Button
|
|
52
|
+
:label="'Today'"
|
|
53
|
+
class="text-sm"
|
|
54
|
+
@click="selectDate(getDate()) || togglePopover()"
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
<div
|
|
58
|
+
class="flex flex-col items-center justify-center p-1 text-gray-800"
|
|
59
|
+
>
|
|
60
|
+
<div class="flex items-center text-xs uppercase">
|
|
45
61
|
<div
|
|
46
|
-
class="
|
|
47
|
-
v-for="(d, i) in ['
|
|
62
|
+
class="flex h-6 w-8 items-center justify-center text-center"
|
|
63
|
+
v-for="(d, i) in ['su', 'mo', 'tu', 'we', 'th', 'fr', 'sa']"
|
|
48
64
|
:key="i"
|
|
49
65
|
>
|
|
50
66
|
{{ d }}
|
|
51
67
|
</div>
|
|
52
68
|
</div>
|
|
53
|
-
<div
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
<div
|
|
70
|
+
class="flex items-center"
|
|
71
|
+
v-for="(week, i) in datesAsWeeks"
|
|
72
|
+
:key="i"
|
|
73
|
+
>
|
|
74
|
+
<div
|
|
75
|
+
v-for="date in week"
|
|
76
|
+
:key="toValue(date)"
|
|
77
|
+
class="flex h-8 w-8 cursor-pointer items-center justify-center rounded hover:bg-gray-50"
|
|
78
|
+
:class="{
|
|
79
|
+
'text-gray-400': date.getMonth() !== currentMonth - 1,
|
|
80
|
+
'font-extrabold text-gray-900':
|
|
81
|
+
toValue(date) === toValue(today),
|
|
82
|
+
'bg-gray-800 text-white hover:bg-gray-800':
|
|
83
|
+
toValue(date) === dateValue,
|
|
84
|
+
}"
|
|
85
|
+
@click="
|
|
86
|
+
() => {
|
|
87
|
+
selectDate(date)
|
|
88
|
+
togglePopover()
|
|
89
|
+
}
|
|
90
|
+
"
|
|
91
|
+
>
|
|
92
|
+
{{ date.getDate() }}
|
|
74
93
|
</div>
|
|
75
94
|
</div>
|
|
76
95
|
</div>
|
|
77
|
-
<div class="
|
|
78
|
-
<
|
|
79
|
-
|
|
96
|
+
<div class="flex justify-end p-1">
|
|
97
|
+
<Button
|
|
98
|
+
:label="'Clear'"
|
|
99
|
+
class="text-sm"
|
|
80
100
|
@click="
|
|
81
101
|
() => {
|
|
82
102
|
selectDate('')
|
|
83
103
|
togglePopover()
|
|
84
104
|
}
|
|
85
105
|
"
|
|
86
|
-
|
|
87
|
-
Clear
|
|
88
|
-
</div>
|
|
106
|
+
/>
|
|
89
107
|
</div>
|
|
90
108
|
</div>
|
|
91
109
|
</template>
|
|
@@ -94,17 +112,27 @@
|
|
|
94
112
|
|
|
95
113
|
<script>
|
|
96
114
|
import Input from './Input.vue'
|
|
97
|
-
import
|
|
115
|
+
import Button from './Button.vue'
|
|
98
116
|
import Popover from './Popover.vue'
|
|
99
|
-
|
|
117
|
+
import FeatherIcon from './FeatherIcon.vue'
|
|
118
|
+
import TextInput from './TextInput.vue'
|
|
100
119
|
export default {
|
|
101
120
|
name: 'DatePicker',
|
|
102
|
-
props: [
|
|
103
|
-
|
|
121
|
+
props: [
|
|
122
|
+
'value',
|
|
123
|
+
'modelValue',
|
|
124
|
+
'placeholder',
|
|
125
|
+
'formatter',
|
|
126
|
+
'readonly',
|
|
127
|
+
'inputClass',
|
|
128
|
+
],
|
|
129
|
+
emits: ['update:modelValue', 'change'],
|
|
104
130
|
components: {
|
|
131
|
+
Popover,
|
|
105
132
|
Input,
|
|
133
|
+
Button,
|
|
106
134
|
FeatherIcon,
|
|
107
|
-
|
|
135
|
+
TextInput,
|
|
108
136
|
},
|
|
109
137
|
data() {
|
|
110
138
|
return {
|
|
@@ -159,17 +187,25 @@ export default {
|
|
|
159
187
|
},
|
|
160
188
|
formatMonth() {
|
|
161
189
|
let date = this.getDate(this.currentYear, this.currentMonth - 1, 1)
|
|
162
|
-
|
|
190
|
+
let month = date.toLocaleString('en-US', {
|
|
191
|
+
month: 'long',
|
|
192
|
+
})
|
|
193
|
+
return `${month}, ${date.getFullYear()}`
|
|
194
|
+
},
|
|
195
|
+
dateValue() {
|
|
196
|
+
return this.value ? this.value : this.modelValue
|
|
163
197
|
},
|
|
164
198
|
},
|
|
165
199
|
methods: {
|
|
166
200
|
selectDate(date) {
|
|
201
|
+
this.$emit('change', this.toValue(date))
|
|
167
202
|
this.$emit('update:modelValue', this.toValue(date))
|
|
168
203
|
},
|
|
169
204
|
selectCurrentMonthYear() {
|
|
170
|
-
let date = this.
|
|
171
|
-
|
|
172
|
-
|
|
205
|
+
let date = this.dateValue ? this.getDate(this.dateValue) : this.getDate()
|
|
206
|
+
if (date === 'Invalid Date') {
|
|
207
|
+
date = this.getDate()
|
|
208
|
+
}
|
|
173
209
|
this.currentYear = date.getFullYear()
|
|
174
210
|
this.currentMonth = date.getMonth() + 1
|
|
175
211
|
},
|
|
@@ -201,7 +237,7 @@ export default {
|
|
|
201
237
|
date = this.getDate(
|
|
202
238
|
date.getFullYear(),
|
|
203
239
|
date.getMonth(),
|
|
204
|
-
date.getDate() + incrementer
|
|
240
|
+
date.getDate() + incrementer,
|
|
205
241
|
)
|
|
206
242
|
dates.push(date)
|
|
207
243
|
count--
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Popover
|
|
3
|
+
@open="selectCurrentMonthYear"
|
|
4
|
+
class="flex w-full [&>div:first-child]:w-full"
|
|
5
|
+
>
|
|
6
|
+
<template #target="{ togglePopover }">
|
|
7
|
+
<Input
|
|
8
|
+
readonly
|
|
9
|
+
type="text"
|
|
10
|
+
icon-left="calendar"
|
|
11
|
+
:placeholder="placeholder"
|
|
12
|
+
:value="dateValue && formatter ? formatDates(dateValue) : dateValue"
|
|
13
|
+
@focus="!readonly ? togglePopover() : null"
|
|
14
|
+
class="w-full"
|
|
15
|
+
:class="inputClass"
|
|
16
|
+
v-bind="$attrs"
|
|
17
|
+
/>
|
|
18
|
+
</template>
|
|
19
|
+
<template #body="{ togglePopover }">
|
|
20
|
+
<div
|
|
21
|
+
class="mt-2 w-fit select-none divide-y rounded-lg bg-white text-base shadow-2xl ring-1 ring-black ring-opacity-5 focus:outline-none"
|
|
22
|
+
>
|
|
23
|
+
<div class="flex items-center p-1 text-gray-500">
|
|
24
|
+
<Button variant="ghost" class="h-7 w-7" @click="prevMonth">
|
|
25
|
+
<FeatherIcon
|
|
26
|
+
:stroke-width="2"
|
|
27
|
+
name="chevron-left"
|
|
28
|
+
class="h-4 w-4"
|
|
29
|
+
/>
|
|
30
|
+
</Button>
|
|
31
|
+
<div class="flex-1 text-center text-base font-medium text-gray-700">
|
|
32
|
+
{{ formatMonth }}
|
|
33
|
+
</div>
|
|
34
|
+
<Button variant="ghost" class="h-7 w-7" @click="nextMonth">
|
|
35
|
+
<FeatherIcon
|
|
36
|
+
:stroke-width="2"
|
|
37
|
+
name="chevron-right"
|
|
38
|
+
class="h-4 w-4"
|
|
39
|
+
/>
|
|
40
|
+
</Button>
|
|
41
|
+
</div>
|
|
42
|
+
<div class="flex items-center justify-center gap-1 p-1">
|
|
43
|
+
<TextInput class="w-28 text-sm" type="text" v-model="fromDate" />
|
|
44
|
+
<TextInput class="w-28 text-sm" type="text" v-model="toDate" />
|
|
45
|
+
</div>
|
|
46
|
+
<div
|
|
47
|
+
class="flex flex-col items-center justify-center p-1 text-gray-800"
|
|
48
|
+
>
|
|
49
|
+
<div class="flex items-center text-xs uppercase">
|
|
50
|
+
<div
|
|
51
|
+
class="flex h-6 w-8 items-center justify-center text-center"
|
|
52
|
+
v-for="(d, i) in ['su', 'mo', 'tu', 'we', 'th', 'fr', 'sa']"
|
|
53
|
+
:key="i"
|
|
54
|
+
>
|
|
55
|
+
{{ d }}
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
<div
|
|
59
|
+
class="flex items-center"
|
|
60
|
+
v-for="(week, i) in datesAsWeeks"
|
|
61
|
+
:key="i"
|
|
62
|
+
>
|
|
63
|
+
<div
|
|
64
|
+
v-for="date in week"
|
|
65
|
+
:key="toValue(date)"
|
|
66
|
+
class="flex h-8 w-8 cursor-pointer items-center justify-center rounded hover:bg-gray-50"
|
|
67
|
+
:class="{
|
|
68
|
+
'text-gray-400': date.getMonth() !== currentMonth - 1,
|
|
69
|
+
'text-gray-900': date.getMonth() === currentMonth - 1,
|
|
70
|
+
'font-extrabold text-gray-900':
|
|
71
|
+
toValue(date) === toValue(today),
|
|
72
|
+
'rounded-none bg-gray-100': isInRange(date),
|
|
73
|
+
'rounded-l-md rounded-r-none bg-gray-800 text-white hover:bg-gray-800':
|
|
74
|
+
fromDate && toValue(date) === toValue(fromDate),
|
|
75
|
+
'rounded-r-md bg-gray-800 text-white hover:bg-gray-800':
|
|
76
|
+
toDate && toValue(date) === toValue(toDate),
|
|
77
|
+
}"
|
|
78
|
+
@click="() => handleDateClick(date)"
|
|
79
|
+
>
|
|
80
|
+
{{ date.getDate() }}
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
<div class="flex justify-end space-x-1 p-1">
|
|
85
|
+
<Button
|
|
86
|
+
:label="'Clear'"
|
|
87
|
+
@click="() => clearDates() | togglePopover()"
|
|
88
|
+
:disabled="!fromDate || !toDate"
|
|
89
|
+
/>
|
|
90
|
+
<Button
|
|
91
|
+
variant="solid"
|
|
92
|
+
:label="'Apply'"
|
|
93
|
+
:disabled="!fromDate || !toDate"
|
|
94
|
+
@click="() => selectDates() | togglePopover()"
|
|
95
|
+
/>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</template>
|
|
99
|
+
</Popover>
|
|
100
|
+
</template>
|
|
101
|
+
|
|
102
|
+
<script>
|
|
103
|
+
import Input from './Input.vue'
|
|
104
|
+
import Button from './Button.vue'
|
|
105
|
+
import Popover from './Popover.vue'
|
|
106
|
+
import FeatherIcon from './FeatherIcon.vue'
|
|
107
|
+
import TextInput from './TextInput.vue'
|
|
108
|
+
export default {
|
|
109
|
+
name: 'DateRangePicker',
|
|
110
|
+
props: ['modelValue', 'placeholder', 'formatter', 'readonly', 'inputClass'],
|
|
111
|
+
emits: ['update:modelValue', 'change'],
|
|
112
|
+
components: {
|
|
113
|
+
Popover,
|
|
114
|
+
Input,
|
|
115
|
+
Button,
|
|
116
|
+
FeatherIcon,
|
|
117
|
+
TextInput,
|
|
118
|
+
},
|
|
119
|
+
data() {
|
|
120
|
+
const fromDate = this.dateValue ? this.dateValue[0] : ''
|
|
121
|
+
const toDate = this.dateValue ? this.dateValue[1] : ''
|
|
122
|
+
return {
|
|
123
|
+
currentYear: null,
|
|
124
|
+
currentMonth: null,
|
|
125
|
+
fromDate,
|
|
126
|
+
toDate,
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
created() {
|
|
130
|
+
this.selectCurrentMonthYear()
|
|
131
|
+
},
|
|
132
|
+
computed: {
|
|
133
|
+
today() {
|
|
134
|
+
return this.getDate()
|
|
135
|
+
},
|
|
136
|
+
datesAsWeeks() {
|
|
137
|
+
let datesAsWeeks = []
|
|
138
|
+
let dates = this.dates.slice()
|
|
139
|
+
while (dates.length) {
|
|
140
|
+
let week = dates.splice(0, 7)
|
|
141
|
+
datesAsWeeks.push(week)
|
|
142
|
+
}
|
|
143
|
+
return datesAsWeeks
|
|
144
|
+
},
|
|
145
|
+
dates() {
|
|
146
|
+
if (!(this.currentYear && this.currentMonth)) {
|
|
147
|
+
return []
|
|
148
|
+
}
|
|
149
|
+
let monthIndex = this.currentMonth - 1
|
|
150
|
+
let year = this.currentYear
|
|
151
|
+
|
|
152
|
+
let firstDayOfMonth = this.getDate(year, monthIndex, 1)
|
|
153
|
+
let lastDayOfMonth = this.getDate(year, monthIndex + 1, 0)
|
|
154
|
+
let leftPaddingCount = firstDayOfMonth.getDay()
|
|
155
|
+
let rightPaddingCount = 6 - lastDayOfMonth.getDay()
|
|
156
|
+
|
|
157
|
+
let leftPadding = this.getDatesAfter(firstDayOfMonth, -leftPaddingCount)
|
|
158
|
+
let rightPadding = this.getDatesAfter(lastDayOfMonth, rightPaddingCount)
|
|
159
|
+
let daysInMonth = this.getDaysInMonth(monthIndex, year)
|
|
160
|
+
let datesInMonth = this.getDatesAfter(firstDayOfMonth, daysInMonth - 1)
|
|
161
|
+
|
|
162
|
+
let dates = [
|
|
163
|
+
...leftPadding,
|
|
164
|
+
firstDayOfMonth,
|
|
165
|
+
...datesInMonth,
|
|
166
|
+
...rightPadding,
|
|
167
|
+
]
|
|
168
|
+
if (dates.length < 42) {
|
|
169
|
+
const finalPadding = this.getDatesAfter(dates.at(-1), 42 - dates.length)
|
|
170
|
+
dates = dates.concat(...finalPadding)
|
|
171
|
+
}
|
|
172
|
+
return dates
|
|
173
|
+
},
|
|
174
|
+
formatMonth() {
|
|
175
|
+
let date = this.getDate(this.currentYear, this.currentMonth - 1, 1)
|
|
176
|
+
let month = date.toLocaleString('en-US', {
|
|
177
|
+
month: 'long',
|
|
178
|
+
})
|
|
179
|
+
return `${month}, ${date.getFullYear()}`
|
|
180
|
+
},
|
|
181
|
+
dateValue() {
|
|
182
|
+
return this.value ? this.value : this.modelValue
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
methods: {
|
|
186
|
+
handleDateClick(date) {
|
|
187
|
+
if (this.fromDate && this.toDate) {
|
|
188
|
+
this.fromDate = this.toValue(date)
|
|
189
|
+
this.toDate = ''
|
|
190
|
+
} else if (this.fromDate && !this.toDate) {
|
|
191
|
+
this.toDate = this.toValue(date)
|
|
192
|
+
} else {
|
|
193
|
+
this.fromDate = this.toValue(date)
|
|
194
|
+
}
|
|
195
|
+
this.swapDatesIfNecessary()
|
|
196
|
+
},
|
|
197
|
+
selectDates() {
|
|
198
|
+
let val = `${this.fromDate},${this.toDate}`
|
|
199
|
+
if (!this.fromDate && !this.toDate) {
|
|
200
|
+
val = ''
|
|
201
|
+
}
|
|
202
|
+
this.$emit('change', val)
|
|
203
|
+
this.$emit('update:modelValue', val)
|
|
204
|
+
},
|
|
205
|
+
swapDatesIfNecessary() {
|
|
206
|
+
if (!this.fromDate || !this.toDate) {
|
|
207
|
+
return
|
|
208
|
+
}
|
|
209
|
+
// if fromDate is greater than toDate, swap them
|
|
210
|
+
let fromDate = this.getDate(this.fromDate)
|
|
211
|
+
let toDate = this.getDate(this.toDate)
|
|
212
|
+
if (fromDate > toDate) {
|
|
213
|
+
let temp = fromDate
|
|
214
|
+
fromDate = toDate
|
|
215
|
+
toDate = temp
|
|
216
|
+
}
|
|
217
|
+
this.fromDate = this.toValue(fromDate)
|
|
218
|
+
this.toDate = this.toValue(toDate)
|
|
219
|
+
},
|
|
220
|
+
selectCurrentMonthYear() {
|
|
221
|
+
let date = this.toDate ? this.getDate(this.toDate) : this.today
|
|
222
|
+
this.currentYear = date.getFullYear()
|
|
223
|
+
this.currentMonth = date.getMonth() + 1
|
|
224
|
+
},
|
|
225
|
+
prevMonth() {
|
|
226
|
+
this.changeMonth(-1)
|
|
227
|
+
},
|
|
228
|
+
nextMonth() {
|
|
229
|
+
this.changeMonth(1)
|
|
230
|
+
},
|
|
231
|
+
changeMonth(adder) {
|
|
232
|
+
this.currentMonth = this.currentMonth + adder
|
|
233
|
+
if (this.currentMonth < 1) {
|
|
234
|
+
this.currentMonth = 12
|
|
235
|
+
this.currentYear = this.currentYear - 1
|
|
236
|
+
}
|
|
237
|
+
if (this.currentMonth > 12) {
|
|
238
|
+
this.currentMonth = 1
|
|
239
|
+
this.currentYear = this.currentYear + 1
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
getDatesAfter(date, count) {
|
|
243
|
+
let incrementer = 1
|
|
244
|
+
if (count < 0) {
|
|
245
|
+
incrementer = -1
|
|
246
|
+
count = Math.abs(count)
|
|
247
|
+
}
|
|
248
|
+
let dates = []
|
|
249
|
+
while (count) {
|
|
250
|
+
date = this.getDate(
|
|
251
|
+
date.getFullYear(),
|
|
252
|
+
date.getMonth(),
|
|
253
|
+
date.getDate() + incrementer,
|
|
254
|
+
)
|
|
255
|
+
dates.push(date)
|
|
256
|
+
count--
|
|
257
|
+
}
|
|
258
|
+
if (incrementer === -1) {
|
|
259
|
+
return dates.reverse()
|
|
260
|
+
}
|
|
261
|
+
return dates
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
getDaysInMonth(monthIndex, year) {
|
|
265
|
+
let daysInMonthMap = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
266
|
+
let daysInMonth = daysInMonthMap[monthIndex]
|
|
267
|
+
if (monthIndex === 1 && this.isLeapYear(year)) {
|
|
268
|
+
return 29
|
|
269
|
+
}
|
|
270
|
+
return daysInMonth
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
isLeapYear(year) {
|
|
274
|
+
if (year % 400 === 0) return true
|
|
275
|
+
if (year % 100 === 0) return false
|
|
276
|
+
if (year % 4 === 0) return true
|
|
277
|
+
return false
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
toValue(date) {
|
|
281
|
+
if (!date) {
|
|
282
|
+
return ''
|
|
283
|
+
}
|
|
284
|
+
if (typeof date === 'string') {
|
|
285
|
+
return date
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// toISOString is buggy and reduces the day by one
|
|
289
|
+
// this is because it considers the UTC timestamp
|
|
290
|
+
// in order to circumvent that we need to use luxon/moment
|
|
291
|
+
// but that refactor could take some time, so fixing the time difference
|
|
292
|
+
// as suggested in this answer.
|
|
293
|
+
// https://stackoverflow.com/a/16084846/3541205
|
|
294
|
+
date.setHours(0, -date.getTimezoneOffset(), 0, 0)
|
|
295
|
+
return date.toISOString().slice(0, 10)
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
getDate(...args) {
|
|
299
|
+
let d = new Date(...args)
|
|
300
|
+
return d
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
isInRange(date) {
|
|
304
|
+
if (!this.fromDate || !this.toDate) {
|
|
305
|
+
return false
|
|
306
|
+
}
|
|
307
|
+
return (
|
|
308
|
+
date >= this.getDate(this.fromDate) && date <= this.getDate(this.toDate)
|
|
309
|
+
)
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
formatDates(value) {
|
|
313
|
+
if (!value) {
|
|
314
|
+
return ''
|
|
315
|
+
}
|
|
316
|
+
const values = value.split(',')
|
|
317
|
+
return this.formatter(values[0]) + ' to ' + this.formatter(values[1])
|
|
318
|
+
},
|
|
319
|
+
clearDates() {
|
|
320
|
+
this.fromDate = ''
|
|
321
|
+
this.toDate = ''
|
|
322
|
+
this.selectDates()
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
}
|
|
326
|
+
</script>
|