@afeefa/vue-app 0.0.351 → 0.0.353
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
0.0.
|
|
1
|
+
0.0.353
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="timePicker">
|
|
2
|
+
<div :class="['timePicker', {nullable}]">
|
|
3
3
|
<div :class="['timeInput inputField', {focus: focusHour}]">
|
|
4
4
|
<div
|
|
5
5
|
class="up iconButton"
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
dense
|
|
16
16
|
outlined
|
|
17
17
|
hide-details
|
|
18
|
+
placeholder="HH"
|
|
18
19
|
:error-messages="error ? ['error'] : []"
|
|
19
20
|
@wheel.prevent="onWheelHour"
|
|
20
21
|
@focus="focusHour = true"
|
|
@@ -50,6 +51,7 @@
|
|
|
50
51
|
dense
|
|
51
52
|
outlined
|
|
52
53
|
hide-details
|
|
54
|
+
placeholder="MM"
|
|
53
55
|
:error-messages="error ? ['error'] : []"
|
|
54
56
|
@wheel.prevent="onWheelMinute"
|
|
55
57
|
@focus="focusMinute = true"
|
|
@@ -65,6 +67,20 @@
|
|
|
65
67
|
</a-icon>
|
|
66
68
|
</div>
|
|
67
69
|
</div>
|
|
70
|
+
|
|
71
|
+
<div
|
|
72
|
+
v-if="nullable && currentDate"
|
|
73
|
+
class="clear"
|
|
74
|
+
>
|
|
75
|
+
<a-icon
|
|
76
|
+
color="primary"
|
|
77
|
+
class="iconButton"
|
|
78
|
+
title="Zeit löschen"
|
|
79
|
+
@click="clear"
|
|
80
|
+
>
|
|
81
|
+
$closeCircleIcon
|
|
82
|
+
</a-icon>
|
|
83
|
+
</div>
|
|
68
84
|
</div>
|
|
69
85
|
</template>
|
|
70
86
|
|
|
@@ -75,7 +91,7 @@ import formatHour from '@a-vue/utils/format-hour'
|
|
|
75
91
|
import formatMinutes from '@a-vue/utils/format-minutes'
|
|
76
92
|
|
|
77
93
|
@Component({
|
|
78
|
-
props: ['value', {error: false}]
|
|
94
|
+
props: ['value', {error: false}, {nullable: false}]
|
|
79
95
|
})
|
|
80
96
|
export default class ATimePicker extends Vue {
|
|
81
97
|
currentDate = null
|
|
@@ -86,23 +102,48 @@ export default class ATimePicker extends Vue {
|
|
|
86
102
|
focusMinute = false
|
|
87
103
|
|
|
88
104
|
created () {
|
|
89
|
-
this.currentDate =
|
|
105
|
+
this.currentDate = this.toDate(this.value)
|
|
90
106
|
|
|
91
107
|
this.initHourAndMinutes()
|
|
92
108
|
}
|
|
93
109
|
|
|
94
110
|
@Watch('value')
|
|
95
111
|
valueChanged () {
|
|
112
|
+
const value = this.toDate(this.value)
|
|
113
|
+
|
|
96
114
|
// filter out inner changes loop
|
|
97
|
-
if (
|
|
115
|
+
if (value?.getTime() === this.currentDate?.getTime()) {
|
|
98
116
|
return
|
|
99
117
|
}
|
|
100
118
|
|
|
101
|
-
this.currentDate =
|
|
119
|
+
this.currentDate = value
|
|
102
120
|
|
|
103
121
|
this.initHourAndMinutes()
|
|
104
122
|
}
|
|
105
123
|
|
|
124
|
+
// an empty value must not become a date: new Date(null) is the epoch,
|
|
125
|
+
// which renders as 01:00 in any timezone east of UTC
|
|
126
|
+
toDate (value) {
|
|
127
|
+
return value ? new Date(value) : null
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
clear () {
|
|
131
|
+
const hadValue = !!this.currentDate
|
|
132
|
+
|
|
133
|
+
this.currentDate = null
|
|
134
|
+
|
|
135
|
+
this.initHourAndMinutes()
|
|
136
|
+
|
|
137
|
+
if (hadValue) {
|
|
138
|
+
this.$emit('input', null)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// a picker without a value starts counting/typing at midnight
|
|
143
|
+
get dateOrMidnight () {
|
|
144
|
+
return this.currentDate || moment().startOf('day').toDate()
|
|
145
|
+
}
|
|
146
|
+
|
|
106
147
|
onWheelHour (wheelEvent) {
|
|
107
148
|
if (!this.focusHour) {
|
|
108
149
|
return
|
|
@@ -125,11 +166,18 @@ export default class ATimePicker extends Vue {
|
|
|
125
166
|
}
|
|
126
167
|
}
|
|
127
168
|
|
|
169
|
+
// a nullable picker shows its placeholder instead of a made up 00:00
|
|
128
170
|
get formattedHour () {
|
|
171
|
+
if (!this.currentDate) {
|
|
172
|
+
return this.nullable ? '' : '00'
|
|
173
|
+
}
|
|
129
174
|
return formatHour(this.currentDate)
|
|
130
175
|
}
|
|
131
176
|
|
|
132
177
|
get formattedMinutes () {
|
|
178
|
+
if (!this.currentDate) {
|
|
179
|
+
return this.nullable ? '' : '00'
|
|
180
|
+
}
|
|
133
181
|
return formatMinutes(this.currentDate)
|
|
134
182
|
}
|
|
135
183
|
|
|
@@ -159,37 +207,55 @@ export default class ATimePicker extends Vue {
|
|
|
159
207
|
}
|
|
160
208
|
|
|
161
209
|
updateHoursAndMinute () {
|
|
210
|
+
this.focusMinute = false
|
|
211
|
+
this.focusHour = false
|
|
212
|
+
|
|
213
|
+
const nothingEntered = !this.currentHour && !this.currentMinutes
|
|
214
|
+
|
|
215
|
+
// clearing both fields is the way back to no time at all
|
|
216
|
+
if (this.nullable) {
|
|
217
|
+
if (nothingEntered) {
|
|
218
|
+
this.clear()
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
} else if (!this.currentDate && !Number(this.currentHour) && !Number(this.currentMinutes)) {
|
|
222
|
+
// without a value, mere focussing must not produce a 00:00
|
|
223
|
+
this.initHourAndMinutes()
|
|
224
|
+
return
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
let date = this.dateOrMidnight
|
|
228
|
+
|
|
162
229
|
if (this.hourValid) {
|
|
163
|
-
|
|
230
|
+
date = moment(date).hours(this.currentHour).toDate()
|
|
164
231
|
}
|
|
165
232
|
|
|
166
233
|
if (this.minutesValid) {
|
|
167
|
-
|
|
234
|
+
date = moment(date).minutes(this.currentMinutes).toDate()
|
|
168
235
|
}
|
|
169
236
|
|
|
170
|
-
this.
|
|
171
|
-
this.focusHour = false
|
|
237
|
+
this.currentDate = date
|
|
172
238
|
|
|
173
239
|
this.dispatchDate()
|
|
174
240
|
}
|
|
175
241
|
|
|
176
242
|
hourUp () {
|
|
177
|
-
this.currentDate = moment(this.
|
|
243
|
+
this.currentDate = moment(this.dateOrMidnight).add(1, 'hours').toDate()
|
|
178
244
|
this.dispatchDate()
|
|
179
245
|
}
|
|
180
246
|
|
|
181
247
|
hourDown () {
|
|
182
|
-
this.currentDate = moment(this.
|
|
248
|
+
this.currentDate = moment(this.dateOrMidnight).subtract(1, 'hours').toDate()
|
|
183
249
|
this.dispatchDate()
|
|
184
250
|
}
|
|
185
251
|
|
|
186
252
|
minutesUp () {
|
|
187
|
-
this.currentDate = moment(this.
|
|
253
|
+
this.currentDate = moment(this.dateOrMidnight).add(15, 'minutes').toDate()
|
|
188
254
|
this.dispatchDate()
|
|
189
255
|
}
|
|
190
256
|
|
|
191
257
|
minutesDown () {
|
|
192
|
-
this.currentDate = moment(this.
|
|
258
|
+
this.currentDate = moment(this.dateOrMidnight).subtract(15, 'minutes').toDate()
|
|
193
259
|
this.dispatchDate()
|
|
194
260
|
}
|
|
195
261
|
|
|
@@ -232,6 +298,24 @@ export default class ATimePicker extends Vue {
|
|
|
232
298
|
.hourMinutesSeparator {
|
|
233
299
|
padding: 0 .4em;
|
|
234
300
|
}
|
|
301
|
+
|
|
302
|
+
// the HH/MM placeholder needs a bit more room than two digits
|
|
303
|
+
|
|
304
|
+
&.nullable .v-input ::v-deep(input) {
|
|
305
|
+
width: 30px;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// only present along with a value, so it takes no room while empty
|
|
309
|
+
|
|
310
|
+
.clear {
|
|
311
|
+
display: flex;
|
|
312
|
+
padding-left: .3em;
|
|
313
|
+
|
|
314
|
+
::v-deep() svg {
|
|
315
|
+
width: 24px;
|
|
316
|
+
height: 24px;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
235
319
|
}
|
|
236
320
|
|
|
237
321
|
.v-input ::v-deep(input) {
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<a-badge
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
v-if="isVisible"
|
|
4
|
+
:color="color_"
|
|
5
|
+
:class="['black--text', { colored: !!color }]"
|
|
5
6
|
inline
|
|
6
7
|
v-bind="$attrs"
|
|
7
8
|
:content="content"
|
|
8
9
|
/>
|
|
9
10
|
</template>
|
|
10
11
|
|
|
11
|
-
|
|
12
12
|
<script>
|
|
13
13
|
import { Component, Vue } from '@a-vue'
|
|
14
14
|
import { modelCountService } from './ModelCountService'
|
|
15
15
|
import { SaveEvent } from '@a-vue/events'
|
|
16
16
|
|
|
17
17
|
@Component({
|
|
18
|
-
props: ['action', 'field', 'count']
|
|
18
|
+
props: ['action', 'field', 'count', 'color', { hideEmpty: false }]
|
|
19
19
|
})
|
|
20
20
|
export default class ModelCount extends Vue {
|
|
21
21
|
countRequest = null
|
|
@@ -32,8 +32,41 @@ export default class ModelCount extends Vue {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
loadCount () {
|
|
36
|
-
this.content = this.countRequest.get(this.field)
|
|
35
|
+
async loadCount () {
|
|
36
|
+
this.content = await this.countRequest.get(this.field)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* hideEmpty: a badge is an attention marker. A permanent zero next to the
|
|
41
|
+
* menu entry is noise that readers learn to skip - and then they miss the one.
|
|
42
|
+
*/
|
|
43
|
+
get isVisible () {
|
|
44
|
+
if (!this.hideEmpty) {
|
|
45
|
+
return true
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return !!Number(this.content)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* color may be a plain string or a function of the loaded count, so a badge
|
|
53
|
+
* can signal "something needs attention" without the caller knowing the
|
|
54
|
+
* number - it is loaded in here.
|
|
55
|
+
*/
|
|
56
|
+
get color_ () {
|
|
57
|
+
if (typeof this.color === 'function') {
|
|
58
|
+
return this.color(Number(this.content))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return this.color || '#EEEEEE'
|
|
37
62
|
}
|
|
38
63
|
}
|
|
39
64
|
</script>
|
|
65
|
+
|
|
66
|
+
<style scoped lang="scss">
|
|
67
|
+
// ABadge paints the badge text grey, which is unreadable on a saturated
|
|
68
|
+
// background. A caller that picks a color gets light text instead.
|
|
69
|
+
.colored :deep(.v-badge__badge) {
|
|
70
|
+
color: #FFFFFF;
|
|
71
|
+
}
|
|
72
|
+
</style>
|