frappe-ui 0.0.27 → 0.0.30
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 +1 -1
- package/src/components/DatePicker.vue +247 -0
- package/src/components/Popover.vue +105 -84
- package/src/index.js +2 -0
- package/src/utils/pageMeta.js +47 -0
package/package.json
CHANGED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Popover @open="selectCurrentMonthYear" transition="default">
|
|
3
|
+
<template #target="{ togglePopover }">
|
|
4
|
+
<Input
|
|
5
|
+
type="text"
|
|
6
|
+
:class="inputClass"
|
|
7
|
+
:value="
|
|
8
|
+
modelValue && formatValue ? formatValue(modelValue) : modelValue || ''
|
|
9
|
+
"
|
|
10
|
+
:placeholder="placeholder"
|
|
11
|
+
@focus="!readonly ? togglePopover() : null"
|
|
12
|
+
readonly
|
|
13
|
+
/>
|
|
14
|
+
</template>
|
|
15
|
+
<template #body-main="{ togglePopover }">
|
|
16
|
+
<div class="p-3 mt-1 text-left select-none">
|
|
17
|
+
<div class="flex items-center justify-between">
|
|
18
|
+
<span class="text-base font-medium text-blue-500">
|
|
19
|
+
{{ formatMonth }}
|
|
20
|
+
</span>
|
|
21
|
+
<span class="flex">
|
|
22
|
+
<div
|
|
23
|
+
class="grid w-5 h-5 rounded-md cursor-pointer hover:bg-gray-100 place-items-center"
|
|
24
|
+
>
|
|
25
|
+
<FeatherIcon
|
|
26
|
+
@click="prevMonth"
|
|
27
|
+
name="chevron-left"
|
|
28
|
+
class="w-4 h-4"
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
<div
|
|
32
|
+
class="grid w-5 h-5 ml-2 rounded-md cursor-pointer hover:bg-gray-100 place-items-center"
|
|
33
|
+
>
|
|
34
|
+
<FeatherIcon
|
|
35
|
+
@click="nextMonth"
|
|
36
|
+
name="chevron-right"
|
|
37
|
+
class="w-4 h-4"
|
|
38
|
+
/>
|
|
39
|
+
</div>
|
|
40
|
+
</span>
|
|
41
|
+
</div>
|
|
42
|
+
<div class="mt-2 text-sm">
|
|
43
|
+
<div class="grid w-full grid-cols-7 text-gray-600 place-items-center">
|
|
44
|
+
<div
|
|
45
|
+
class="grid w-6 h-6 gap-1 text-center place-items-center"
|
|
46
|
+
v-for="(d, i) in ['S', 'M', 'T', 'W', 'T', 'F', 'S']"
|
|
47
|
+
:key="i"
|
|
48
|
+
>
|
|
49
|
+
{{ d }}
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
<div v-for="(week, i) in datesAsWeeks" :key="i" class="mt-1">
|
|
53
|
+
<div class="grid w-full grid-cols-7 gap-1 place-items-center">
|
|
54
|
+
<div
|
|
55
|
+
v-for="date in week"
|
|
56
|
+
:key="toValue(date)"
|
|
57
|
+
class="grid w-6 h-6 rounded-md cursor-pointer place-items-center hover:bg-blue-100 hover:text-blue-700"
|
|
58
|
+
:class="{
|
|
59
|
+
'text-gray-600': date.getMonth() !== currentMonth - 1,
|
|
60
|
+
'text-blue-500': toValue(date) === toValue(today),
|
|
61
|
+
'bg-blue-100 font-semibold text-blue-500':
|
|
62
|
+
toValue(date) === modelValue,
|
|
63
|
+
}"
|
|
64
|
+
@click="
|
|
65
|
+
() => {
|
|
66
|
+
selectDate(date)
|
|
67
|
+
togglePopover()
|
|
68
|
+
}
|
|
69
|
+
"
|
|
70
|
+
>
|
|
71
|
+
{{ date.getDate() }}
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
<div class="flex justify-end w-full mt-2">
|
|
77
|
+
<div
|
|
78
|
+
class="px-2 py-1 text-sm rounded-md cursor-pointer hover:bg-gray-100"
|
|
79
|
+
@click="
|
|
80
|
+
() => {
|
|
81
|
+
selectDate('')
|
|
82
|
+
togglePopover()
|
|
83
|
+
}
|
|
84
|
+
"
|
|
85
|
+
>
|
|
86
|
+
Clear
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
</template>
|
|
91
|
+
</Popover>
|
|
92
|
+
</template>
|
|
93
|
+
|
|
94
|
+
<script>
|
|
95
|
+
import Popover from './Popover.vue'
|
|
96
|
+
|
|
97
|
+
export default {
|
|
98
|
+
name: 'DatePicker',
|
|
99
|
+
props: ['modelValue', 'placeholder', 'readonly', 'formatValue', 'inputClass'],
|
|
100
|
+
emits: ['update:modelValue'],
|
|
101
|
+
components: {
|
|
102
|
+
Popover,
|
|
103
|
+
},
|
|
104
|
+
data() {
|
|
105
|
+
return {
|
|
106
|
+
currentYear: null,
|
|
107
|
+
currentMonth: null,
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
created() {
|
|
111
|
+
this.selectCurrentMonthYear()
|
|
112
|
+
},
|
|
113
|
+
computed: {
|
|
114
|
+
today() {
|
|
115
|
+
return this.getDate()
|
|
116
|
+
},
|
|
117
|
+
datesAsWeeks() {
|
|
118
|
+
let datesAsWeeks = []
|
|
119
|
+
let dates = this.dates.slice()
|
|
120
|
+
while (dates.length) {
|
|
121
|
+
let week = dates.splice(0, 7)
|
|
122
|
+
datesAsWeeks.push(week)
|
|
123
|
+
}
|
|
124
|
+
return datesAsWeeks
|
|
125
|
+
},
|
|
126
|
+
dates() {
|
|
127
|
+
if (!(this.currentYear && this.currentMonth)) {
|
|
128
|
+
return []
|
|
129
|
+
}
|
|
130
|
+
let monthIndex = this.currentMonth - 1
|
|
131
|
+
let year = this.currentYear
|
|
132
|
+
|
|
133
|
+
let firstDayOfMonth = this.getDate(year, monthIndex, 1)
|
|
134
|
+
let lastDayOfMonth = this.getDate(year, monthIndex + 1, 0)
|
|
135
|
+
let leftPaddingCount = firstDayOfMonth.getDay()
|
|
136
|
+
let rightPaddingCount = 6 - lastDayOfMonth.getDay()
|
|
137
|
+
|
|
138
|
+
let leftPadding = this.getDatesAfter(firstDayOfMonth, -leftPaddingCount)
|
|
139
|
+
let rightPadding = this.getDatesAfter(lastDayOfMonth, rightPaddingCount)
|
|
140
|
+
let daysInMonth = this.getDaysInMonth(monthIndex, year)
|
|
141
|
+
let datesInMonth = this.getDatesAfter(firstDayOfMonth, daysInMonth - 1)
|
|
142
|
+
|
|
143
|
+
let dates = [
|
|
144
|
+
...leftPadding,
|
|
145
|
+
firstDayOfMonth,
|
|
146
|
+
...datesInMonth,
|
|
147
|
+
...rightPadding,
|
|
148
|
+
]
|
|
149
|
+
if (dates.length < 42) {
|
|
150
|
+
const finalPadding = this.getDatesAfter(dates.at(-1), 42 - dates.length)
|
|
151
|
+
dates = dates.concat(...finalPadding)
|
|
152
|
+
}
|
|
153
|
+
return dates
|
|
154
|
+
},
|
|
155
|
+
formatMonth() {
|
|
156
|
+
let date = this.getDate(this.currentYear, this.currentMonth - 1, 1)
|
|
157
|
+
return date.toLocaleString('en-US', { month: 'short', year: 'numeric' })
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
methods: {
|
|
161
|
+
selectDate(date) {
|
|
162
|
+
this.$emit('update:modelValue', this.toValue(date))
|
|
163
|
+
},
|
|
164
|
+
selectCurrentMonthYear() {
|
|
165
|
+
let date = this.modelValue
|
|
166
|
+
? this.getDate(this.modelValue)
|
|
167
|
+
: this.getDate()
|
|
168
|
+
this.currentYear = date.getFullYear()
|
|
169
|
+
this.currentMonth = date.getMonth() + 1
|
|
170
|
+
},
|
|
171
|
+
prevMonth() {
|
|
172
|
+
this.changeMonth(-1)
|
|
173
|
+
},
|
|
174
|
+
nextMonth() {
|
|
175
|
+
this.changeMonth(1)
|
|
176
|
+
},
|
|
177
|
+
changeMonth(adder) {
|
|
178
|
+
this.currentMonth = this.currentMonth + adder
|
|
179
|
+
if (this.currentMonth < 1) {
|
|
180
|
+
this.currentMonth = 12
|
|
181
|
+
this.currentYear = this.currentYear - 1
|
|
182
|
+
}
|
|
183
|
+
if (this.currentMonth > 12) {
|
|
184
|
+
this.currentMonth = 1
|
|
185
|
+
this.currentYear = this.currentYear + 1
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
getDatesAfter(date, count) {
|
|
189
|
+
let incrementer = 1
|
|
190
|
+
if (count < 0) {
|
|
191
|
+
incrementer = -1
|
|
192
|
+
count = Math.abs(count)
|
|
193
|
+
}
|
|
194
|
+
let dates = []
|
|
195
|
+
while (count) {
|
|
196
|
+
date = this.getDate(
|
|
197
|
+
date.getFullYear(),
|
|
198
|
+
date.getMonth(),
|
|
199
|
+
date.getDate() + incrementer
|
|
200
|
+
)
|
|
201
|
+
dates.push(date)
|
|
202
|
+
count--
|
|
203
|
+
}
|
|
204
|
+
if (incrementer === -1) {
|
|
205
|
+
return dates.reverse()
|
|
206
|
+
}
|
|
207
|
+
return dates
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
getDaysInMonth(monthIndex, year) {
|
|
211
|
+
let daysInMonthMap = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
212
|
+
let daysInMonth = daysInMonthMap[monthIndex]
|
|
213
|
+
if (monthIndex === 1 && this.isLeapYear(year)) {
|
|
214
|
+
return 29
|
|
215
|
+
}
|
|
216
|
+
return daysInMonth
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
isLeapYear(year) {
|
|
220
|
+
if (year % 400 === 0) return true
|
|
221
|
+
if (year % 100 === 0) return false
|
|
222
|
+
if (year % 4 === 0) return true
|
|
223
|
+
return false
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
toValue(date) {
|
|
227
|
+
if (!date) {
|
|
228
|
+
return ''
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// toISOString is buggy and reduces the day by one
|
|
232
|
+
// this is because it considers the UTC timestamp
|
|
233
|
+
// in order to circumvent that we need to use luxon/moment
|
|
234
|
+
// but that refactor could take some time, so fixing the time difference
|
|
235
|
+
// as suggested in this answer.
|
|
236
|
+
// https://stackoverflow.com/a/16084846/3541205
|
|
237
|
+
date.setHours(0, -date.getTimezoneOffset(), 0, 0)
|
|
238
|
+
return date.toISOString().slice(0, 10)
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
getDate(...args) {
|
|
242
|
+
let d = new Date(...args)
|
|
243
|
+
return d
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
}
|
|
247
|
+
</script>
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div ref="reference">
|
|
3
3
|
<div
|
|
4
|
-
class="h-full"
|
|
5
4
|
ref="target"
|
|
5
|
+
class="inline-block"
|
|
6
6
|
@click="updatePosition"
|
|
7
7
|
@focusin="updatePosition"
|
|
8
8
|
@keydown="updatePosition"
|
|
9
|
+
@mouseover="onMouseover"
|
|
10
|
+
@mouseleave="onMouseleave"
|
|
9
11
|
>
|
|
10
12
|
<slot
|
|
11
13
|
name="target"
|
|
12
14
|
v-bind="{ togglePopover, updatePosition, open, close, isOpen }"
|
|
13
|
-
|
|
15
|
+
/>
|
|
14
16
|
</div>
|
|
15
17
|
<teleport to="#frappeui-popper-root">
|
|
16
18
|
<div
|
|
@@ -20,17 +22,25 @@
|
|
|
20
22
|
:style="{ minWidth: targetWidth ? targetWidth + 'px' : null }"
|
|
21
23
|
v-show="isOpen"
|
|
22
24
|
>
|
|
23
|
-
<transition v-bind="
|
|
25
|
+
<transition v-bind="popupTransition">
|
|
24
26
|
<div v-show="isOpen">
|
|
25
|
-
<div
|
|
26
|
-
v-if="!hideArrow"
|
|
27
|
-
class="popover-arrow"
|
|
28
|
-
ref="popover-arrow"
|
|
29
|
-
></div>
|
|
30
27
|
<slot
|
|
31
|
-
name="
|
|
28
|
+
name="body"
|
|
32
29
|
v-bind="{ togglePopover, updatePosition, open, close, isOpen }"
|
|
33
|
-
|
|
30
|
+
>
|
|
31
|
+
<div class="bg-white border border-gray-100 rounded-lg shadow-xl">
|
|
32
|
+
<slot
|
|
33
|
+
name="body-main"
|
|
34
|
+
v-bind="{
|
|
35
|
+
togglePopover,
|
|
36
|
+
updatePosition,
|
|
37
|
+
open,
|
|
38
|
+
close,
|
|
39
|
+
isOpen,
|
|
40
|
+
}"
|
|
41
|
+
/>
|
|
42
|
+
</div>
|
|
43
|
+
</slot>
|
|
34
44
|
</div>
|
|
35
45
|
</transition>
|
|
36
46
|
</div>
|
|
@@ -44,12 +54,16 @@ import { createPopper } from '@popperjs/core'
|
|
|
44
54
|
export default {
|
|
45
55
|
name: 'Popover',
|
|
46
56
|
props: {
|
|
47
|
-
hideArrow: {
|
|
48
|
-
type: Boolean,
|
|
49
|
-
default: true,
|
|
50
|
-
},
|
|
51
57
|
show: {
|
|
52
|
-
default:
|
|
58
|
+
default: undefined,
|
|
59
|
+
},
|
|
60
|
+
trigger: {
|
|
61
|
+
type: String,
|
|
62
|
+
default: 'click', // click, hover
|
|
63
|
+
},
|
|
64
|
+
hoverDelay: {
|
|
65
|
+
type: Number,
|
|
66
|
+
default: 0,
|
|
53
67
|
},
|
|
54
68
|
right: Boolean,
|
|
55
69
|
placement: {
|
|
@@ -58,29 +72,25 @@ export default {
|
|
|
58
72
|
},
|
|
59
73
|
popoverClass: [String, Object, Array],
|
|
60
74
|
transition: {
|
|
61
|
-
type: Object,
|
|
62
75
|
default: null,
|
|
63
76
|
},
|
|
64
77
|
},
|
|
65
|
-
emits: ['
|
|
66
|
-
watch: {
|
|
67
|
-
show: {
|
|
68
|
-
immediate: true,
|
|
69
|
-
handler(val) {
|
|
70
|
-
if (val) {
|
|
71
|
-
this.open()
|
|
72
|
-
} else {
|
|
73
|
-
this.close()
|
|
74
|
-
}
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
+
emits: ['open', 'close', 'update:show'],
|
|
78
79
|
data() {
|
|
79
80
|
return {
|
|
80
|
-
|
|
81
|
+
showPopup: false,
|
|
81
82
|
targetWidth: null,
|
|
82
83
|
}
|
|
83
84
|
},
|
|
85
|
+
watch: {
|
|
86
|
+
show(val) {
|
|
87
|
+
if (val) {
|
|
88
|
+
this.open()
|
|
89
|
+
} else {
|
|
90
|
+
this.close()
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
},
|
|
84
94
|
created() {
|
|
85
95
|
if (!document.getElementById('frappeui-popper-root')) {
|
|
86
96
|
const root = document.createElement('div')
|
|
@@ -99,7 +109,7 @@ export default {
|
|
|
99
109
|
}
|
|
100
110
|
this.close()
|
|
101
111
|
}
|
|
102
|
-
if (this.
|
|
112
|
+
if (!this.showPropPassed) {
|
|
103
113
|
document.addEventListener('click', this.listener)
|
|
104
114
|
}
|
|
105
115
|
this.$nextTick(() => {
|
|
@@ -110,37 +120,65 @@ export default {
|
|
|
110
120
|
this.popper && this.popper.destroy()
|
|
111
121
|
document.removeEventListener('click', this.listener)
|
|
112
122
|
},
|
|
123
|
+
computed: {
|
|
124
|
+
showPropPassed() {
|
|
125
|
+
return this.show != null
|
|
126
|
+
},
|
|
127
|
+
isOpen: {
|
|
128
|
+
get() {
|
|
129
|
+
if (this.showPropPassed) {
|
|
130
|
+
return this.show
|
|
131
|
+
}
|
|
132
|
+
return this.showPopup
|
|
133
|
+
},
|
|
134
|
+
set(val) {
|
|
135
|
+
val = Boolean(val)
|
|
136
|
+
if (this.showPropPassed) {
|
|
137
|
+
this.$emit('update:show', val)
|
|
138
|
+
} else {
|
|
139
|
+
this.showPopup = val
|
|
140
|
+
}
|
|
141
|
+
if (val === false) {
|
|
142
|
+
this.$emit('close')
|
|
143
|
+
} else if (val === true) {
|
|
144
|
+
this.$emit('open')
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
popupTransition() {
|
|
149
|
+
let templates = {
|
|
150
|
+
default: {
|
|
151
|
+
enterActiveClass: 'transition duration-200 ease-out',
|
|
152
|
+
enterFromClass: 'translate-y-1 opacity-0',
|
|
153
|
+
enterToClass: 'translate-y-0 opacity-100',
|
|
154
|
+
leaveActiveClass: 'transition duration-150 ease-in',
|
|
155
|
+
leaveFromClass: 'translate-y-0 opacity-100',
|
|
156
|
+
leaveToClass: 'translate-y-1 opacity-0',
|
|
157
|
+
},
|
|
158
|
+
}
|
|
159
|
+
if (typeof this.transition === 'string') {
|
|
160
|
+
return templates[this.transition]
|
|
161
|
+
}
|
|
162
|
+
return this.transition
|
|
163
|
+
},
|
|
164
|
+
},
|
|
113
165
|
methods: {
|
|
114
166
|
setupPopper() {
|
|
115
167
|
if (!this.popper) {
|
|
116
168
|
this.popper = createPopper(this.$refs.reference, this.$refs.popover, {
|
|
117
169
|
placement: this.placement,
|
|
118
|
-
modifiers: !this.hideArrow
|
|
119
|
-
? [
|
|
120
|
-
{
|
|
121
|
-
name: 'arrow',
|
|
122
|
-
options: {
|
|
123
|
-
element: this.$refs['popover-arrow'],
|
|
124
|
-
},
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
name: 'offset',
|
|
128
|
-
options: {
|
|
129
|
-
offset: [0, 10],
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
]
|
|
133
|
-
: [],
|
|
134
170
|
})
|
|
135
171
|
} else {
|
|
136
172
|
this.updatePosition()
|
|
137
173
|
}
|
|
138
|
-
this.$emit('init')
|
|
139
174
|
},
|
|
140
175
|
updatePosition() {
|
|
141
176
|
this.popper && this.popper.update()
|
|
142
177
|
},
|
|
143
178
|
togglePopover(flag) {
|
|
179
|
+
if (flag instanceof Event) {
|
|
180
|
+
flag = null
|
|
181
|
+
}
|
|
144
182
|
if (flag == null) {
|
|
145
183
|
flag = !this.isOpen
|
|
146
184
|
}
|
|
@@ -157,49 +195,32 @@ export default {
|
|
|
157
195
|
}
|
|
158
196
|
this.isOpen = true
|
|
159
197
|
this.$nextTick(() => this.setupPopper())
|
|
160
|
-
this.$emit('open')
|
|
161
198
|
},
|
|
162
199
|
close() {
|
|
163
200
|
if (!this.isOpen) {
|
|
164
201
|
return
|
|
165
202
|
}
|
|
166
203
|
this.isOpen = false
|
|
167
|
-
|
|
204
|
+
},
|
|
205
|
+
onMouseover() {
|
|
206
|
+
if (this.trigger === 'hover') {
|
|
207
|
+
if (this.hoverDelay) {
|
|
208
|
+
this.hoverTimer = setTimeout(() => {
|
|
209
|
+
this.open()
|
|
210
|
+
}, Number(this.hoverDelay) * 1000)
|
|
211
|
+
} else {
|
|
212
|
+
this.open()
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
onMouseleave() {
|
|
217
|
+
if (this.hoverTimer) {
|
|
218
|
+
clearTimeout(this.hoverTimer)
|
|
219
|
+
}
|
|
220
|
+
if (this.trigger === 'hover') {
|
|
221
|
+
this.close()
|
|
222
|
+
}
|
|
168
223
|
},
|
|
169
224
|
},
|
|
170
225
|
}
|
|
171
226
|
</script>
|
|
172
|
-
<style scoped>
|
|
173
|
-
.popover-arrow,
|
|
174
|
-
.popover-arrow::after {
|
|
175
|
-
position: absolute;
|
|
176
|
-
width: theme('spacing.4');
|
|
177
|
-
height: theme('spacing.4');
|
|
178
|
-
z-index: -1;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
.popover-arrow::after {
|
|
182
|
-
content: '';
|
|
183
|
-
background: white;
|
|
184
|
-
transform: rotate(45deg);
|
|
185
|
-
border-top: 1px solid theme('borderColor.gray.400');
|
|
186
|
-
border-left: 1px solid theme('borderColor.gray.400');
|
|
187
|
-
border-top-left-radius: 6px;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
.popover-container[data-popper-placement^='top'] > .popover-arrow {
|
|
191
|
-
bottom: calc(theme('spacing.2') * -1);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
.popover-container[data-popper-placement^='bottom'] > .popover-arrow {
|
|
195
|
-
top: calc(theme('spacing.2') * -1);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
.popover-container[data-popper-placement^='left'] > .popover-arrow {
|
|
199
|
-
right: calc(theme('spacing.2') * -1);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
.popover-container[data-popper-placement^='right'] > .popover-arrow {
|
|
203
|
-
left: calc(theme('spacing.2') * -1);
|
|
204
|
-
}
|
|
205
|
-
</style>
|
package/src/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export { default as Avatar } from './components/Avatar.vue'
|
|
|
4
4
|
export { default as Badge } from './components/Badge.vue'
|
|
5
5
|
export { default as Button } from './components/Button.vue'
|
|
6
6
|
export { default as Card } from './components/Card.vue'
|
|
7
|
+
export { default as DatePicker } from './components/DatePicker.vue'
|
|
7
8
|
export { default as Dialog } from './components/Dialog.vue'
|
|
8
9
|
export { default as Dropdown } from './components/Dropdown.vue'
|
|
9
10
|
export { default as ErrorMessage } from './components/ErrorMessage.vue'
|
|
@@ -29,6 +30,7 @@ export { default as onOutsideClickDirective } from './directives/onOutsideClick.
|
|
|
29
30
|
export { default as call, createCall } from './utils/call.js'
|
|
30
31
|
export { default as debounce } from './utils/debounce.js'
|
|
31
32
|
export { createResource } from './utils/resources.js'
|
|
33
|
+
export { default as pageMeta } from './utils/pageMeta.js'
|
|
32
34
|
|
|
33
35
|
// plugin
|
|
34
36
|
export { default as FrappeUI } from './utils/plugin.js'
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { watch } from 'vue'
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
install(app) {
|
|
5
|
+
app.mixin(createMixin())
|
|
6
|
+
},
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function createMixin() {
|
|
10
|
+
let faviconRef = document.querySelector('link[rel="icon"]')
|
|
11
|
+
let defaultFavIcon = faviconRef.href
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
created() {
|
|
15
|
+
if (this.$options.pageMeta) {
|
|
16
|
+
watch(
|
|
17
|
+
() => {
|
|
18
|
+
try {
|
|
19
|
+
return this.$options.pageMeta.call(this)
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.warn('Failed to parse pageMeta\n\n', error)
|
|
22
|
+
return null
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
(pageMeta) => {
|
|
26
|
+
if (!pageMeta) return
|
|
27
|
+
if (pageMeta.title) {
|
|
28
|
+
document.title = pageMeta.title
|
|
29
|
+
}
|
|
30
|
+
if (pageMeta.emoji) {
|
|
31
|
+
let href = `data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>${pageMeta.emoji}</text></svg>`
|
|
32
|
+
faviconRef.href = href
|
|
33
|
+
} else if (pageMeta.icon) {
|
|
34
|
+
faviconRef.href = pageMeta.icon
|
|
35
|
+
} else {
|
|
36
|
+
faviconRef.href = defaultFavIcon
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
immediate: true,
|
|
41
|
+
deep: true,
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
}
|