@afeefa/vue-app 0.0.132 → 0.0.134

Sign up to get free protection for your applications and to get access to all the features.
@@ -1 +1 @@
1
- 0.0.132
1
+ 0.0.134
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afeefa/vue-app",
3
- "version": "0.0.132",
3
+ "version": "0.0.134",
4
4
  "description": "",
5
5
  "author": "Afeefa Kollektiv <kollektiv@afeefa.de>",
6
6
  "license": "MIT",
@@ -1,38 +1,44 @@
1
1
  <template>
2
- <v-menu
3
- v-model="menu"
4
- :close-on-content-click="false"
5
- transition="scale-transition"
6
- offset-y
7
- min-width="auto"
8
- >
9
- <template #activator="{ on, attrs }">
10
- <v-text-field
11
- ref="input"
12
- :value="formattedDate"
13
- :label="label"
14
- :style="cwm_widthStyle"
15
- v-bind="{...$attrs, ...attrs, dense, outlined}"
16
- :rules="validationRules"
17
- :error-messages="errorMessages"
18
- :readonly="type === 'month'"
19
- v-on="on"
20
- @input="dateInputChanged"
21
- @click:clear="clearDate"
22
- @click.native="on.click"
23
- />
24
- </template>
25
-
26
- <v-date-picker
27
- v-if="menu"
28
- :value="date"
29
- no-title
30
- :type="type"
31
- v-bind="$attrs"
32
- :first-day-of-week="1"
33
- @input="dateChanged"
2
+ <div :class="['a-date-picker', {'type--month': type === 'month'}]">
3
+ <v-text-field
4
+ ref="input"
5
+ :value="formattedDate"
6
+ :label="label"
7
+ :placeholder="type === 'month' ? '' : 'DD.MM.JJJJ'"
8
+ :style="cwm_widthStyle"
9
+ v-bind="{...$attrs, dense, outlined}"
10
+ :error-messages="errorMessages"
11
+ append-icon="$calendarIcon"
12
+ :readonly="type === 'month'"
13
+ :clearable="clearable"
14
+ @input="dateInputChanged"
15
+ @click:clear="clearDate"
16
+ @click:append="open"
17
+ @mouseup="openIfMonth"
34
18
  />
35
- </v-menu>
19
+
20
+ <v-overlay
21
+ :value="isOpen"
22
+ :z-index="299"
23
+ :opacity="0"
24
+ />
25
+
26
+ <div :class="popUpCssClass">
27
+ <div
28
+ v-if="isOpen"
29
+ class="popUpContent elevation-6"
30
+ >
31
+ <v-date-picker
32
+ :value="date"
33
+ no-title
34
+ :type="type"
35
+ v-bind="$attrs"
36
+ :first-day-of-week="1"
37
+ @input="dateChanged"
38
+ />
39
+ </div>
40
+ </div>
41
+ </div>
36
42
  </template>
37
43
 
38
44
 
@@ -40,22 +46,22 @@
40
46
  import { Component, Mixins, Watch } from '@a-vue'
41
47
  import { formatDate } from '@a-vue/utils/format-date'
42
48
  import { ComponentWidthMixin } from './mixins/ComponentWidthMixin'
49
+ import { UsesPositionServiceMixin } from '../services/position/UsesPositionServiceMixin'
50
+ import { CancelOnEscMixin } from '@a-vue/services/escape/CancelOnEscMixin'
51
+ import { PositionConfig } from '../services/PositionService'
52
+ import { randomCssClass } from '../utils/random'
43
53
 
44
54
  @Component({
45
55
  props: ['value', 'validator', 'type', {dense: true, outlined: true}]
46
56
  })
47
- export default class ADatePicker extends Mixins(ComponentWidthMixin) {
57
+ export default class ADatePicker extends Mixins(ComponentWidthMixin, UsesPositionServiceMixin, CancelOnEscMixin) {
48
58
  value_ = null
49
- menu = false
50
59
  errorMessages = []
60
+ popUpId = randomCssClass(10)
61
+ isOpen = false
51
62
 
52
63
  created () {
53
- if (this.value) {
54
- this.value_ = this.value
55
- } else {
56
- this.value_Changed() // force validation if date is null
57
- }
58
- this.$emit('input', this.value_)
64
+ this.value_ = this.value
59
65
  }
60
66
 
61
67
  mounted () {
@@ -65,27 +71,112 @@ export default class ADatePicker extends Mixins(ComponentWidthMixin) {
65
71
  @Watch('value')
66
72
  valueChanged () {
67
73
  this.value_ = this.value
74
+ this.validate()
68
75
  }
69
76
 
70
- @Watch('value_')
71
- value_Changed () { // validate on any change
72
- this.validateDateValue()
77
+ get clearable () {
78
+ if (this.validator && this.validator.param('null') === false) {
79
+ return false
80
+ }
81
+ return true
73
82
  }
74
83
 
75
- validateDateValue () {
76
- this.errorMessages = []
77
- if (this.validator) {
78
- const rules = this.validator.getRules(this.label)
79
- for (const rule of rules) {
80
- const message = rule(this.value_)
81
- if (typeof message === 'string') {
82
- this.errorMessages.push(message)
83
- break
84
- }
85
- }
84
+ openIfMonth () {
85
+ if (this.type === 'month') {
86
+ this.open()
86
87
  }
87
88
  }
88
89
 
90
+ open () {
91
+ window.addEventListener('mousedown', this.onClickOutside)
92
+
93
+ const container = this.getContainer()
94
+
95
+ container.appendChild(this.popUp)
96
+ this.positionize()
97
+
98
+ this.isOpen = true
99
+
100
+ this.coe_watchCancel()
101
+
102
+ this.$emit('open')
103
+ }
104
+
105
+ close () {
106
+ if (!this.isOpen) {
107
+ return
108
+ }
109
+
110
+ this.urp_unregisterPositionWatchers()
111
+
112
+ window.removeEventListener('mousedown', this.onClickOutside)
113
+
114
+ this.$el.appendChild(this.popUp)
115
+
116
+ this.isOpen = false
117
+
118
+ this.coe_unwatchCancel()
119
+
120
+ this.$emit('close')
121
+ }
122
+
123
+ positionize () {
124
+ const position = new PositionConfig()
125
+ .setAnchor(this.$refs.input, '.v-input__slot')
126
+ .setTarget(
127
+ document.documentElement,
128
+ '.' + this.popUpCssClass + ' .popUpContent'
129
+ )
130
+ .targetTop()
131
+ .anchorBottom()
132
+ .diffY('.5rem')
133
+ .alternativeY(p => {
134
+ p.targetBottom().anchorTop().diffY('-.5rem')
135
+ })
136
+
137
+ this.urp_registerPositionWatcher(position)
138
+ }
139
+
140
+ get popUpCssClass () {
141
+ return 'popUp-' + this.popUpId
142
+ }
143
+
144
+ getContainer () {
145
+ return document.querySelector('.v-application')
146
+ }
147
+
148
+ get popUp () {
149
+ const container = this.getContainer()
150
+ return container.querySelector('.' + this.popUpCssClass)
151
+ }
152
+
153
+ coe_cancelOnEsc () {
154
+ this.close()
155
+ return false // stop esc propagation
156
+ }
157
+
158
+ onClickOutside (e) {
159
+ const popUp = this.popUp
160
+
161
+ // no popup present
162
+ if (!popUp) {
163
+ return
164
+ }
165
+
166
+ // popup clicked
167
+ if (popUp.contains(e.target)) {
168
+ return
169
+ }
170
+
171
+ // activator clicked
172
+ const activator = this.$refs.input.$el
173
+ if (activator && activator.contains(e.target)) {
174
+ return
175
+ }
176
+
177
+ this.close()
178
+ }
179
+
89
180
  get date () {
90
181
  return this.value_
91
182
  ? this.value_.toISOString().substr(0, this.type === 'month' ? 7 : 10)
@@ -98,25 +189,28 @@ export default class ADatePicker extends Mixins(ComponentWidthMixin) {
98
189
 
99
190
  clearDate () {
100
191
  this.value_ = null
192
+ this.validate()
101
193
  this.$emit('input', this.value_)
102
194
  }
103
195
 
104
- isDate (value) {
196
+ textInputIsDate (value) {
105
197
  return value && value.match(/^(3[01]|[12][0-9]|0?[1-9]).(1[012]|0?[1-9]).((?:19|20)\d{2})$/)
106
198
  }
107
199
 
108
200
  dateInputChanged (value) {
109
201
  if (!value) {
110
202
  this.dateChanged(null)
111
- } else if (this.isDate(value)) {
203
+ } else if (this.validateTextInput(value)) {
112
204
  const [day, month, year] = value.split('.')
113
205
  this.dateChanged(new Date(year + '-' + month + '-' + day))
114
206
  }
115
207
  }
116
208
 
117
209
  dateChanged (date) {
118
- this.menu = false
119
210
  this.value_ = date ? new Date(date) : null
211
+ this.validate()
212
+
213
+ this.close()
120
214
  this.$emit('input', this.value_)
121
215
  }
122
216
 
@@ -133,31 +227,68 @@ export default class ADatePicker extends Mixins(ComponentWidthMixin) {
133
227
  }
134
228
 
135
229
  validate () {
136
- this.validateDateValue()
230
+ this.errorMessages = []
231
+ if (this.validator) {
232
+ const rules = this.validator.getRules(this.label)
233
+ for (const rule of rules) {
234
+ const message = rule(this.value_)
235
+ if (typeof message === 'string') {
236
+ this.errorMessages.push(message)
237
+ break
238
+ }
239
+ }
240
+ }
137
241
  }
138
242
 
139
- get validationRules () {
140
- if (this.type !== 'month') {
141
- const dateFormat = v => {
142
- if (v && !this.isDate(v)) {
143
- return 'Das Datum sollte das Format TT.MM.JJJJ haben.'
144
- }
145
- return true
243
+ validateTextInput (value) {
244
+ this.errorMessages = []
245
+ const rules = this.textInputValidationRules
246
+ for (const rule of rules) {
247
+ const message = rule(value)
248
+ if (typeof message === 'string') {
249
+ this.errorMessages.push(message)
250
+ break
146
251
  }
147
- return [dateFormat]
148
252
  }
253
+ return !this.errorMessages.length
254
+ }
255
+
256
+ get textInputValidationRules () {
257
+ if (this.type === 'month') {
258
+ return []
259
+ }
260
+
261
+ return [v => {
262
+ if (v && !this.textInputIsDate(v)) {
263
+ return 'Das Datum sollte das Format TT.MM.JJJJ haben.'
264
+ }
265
+ return true
266
+ }]
267
+ }
268
+
269
+ get dateValidationRules () {
270
+ if (this.validator) {
271
+ return this.validator.getRules(this.label)
272
+ }
273
+ return []
149
274
  }
150
275
  }
151
276
  </script>
152
277
 
153
278
 
154
279
  <style lang="scss" scoped>
155
- :deep(.v-select__slot) {
156
- cursor: pointer;
280
+ .popUpContent {
281
+ min-height: 2.2rem;
282
+ position: absolute;
283
+ z-index: 400;
284
+ display: block;
285
+ background-color: white;
286
+ padding: .5rem;
287
+ transition: left .2s;
288
+ }
157
289
 
158
- input {
159
- cursor: pointer;
160
- }
290
+ .a-date-picker.type--month :deep(input) {
291
+ cursor: pointer;
161
292
  }
162
293
 
163
294
  .v-text-field :deep(.v-input__icon--clear) { // always show clear icon, https://github.com/vuetifyjs/vuetify/pull/15876
@@ -41,7 +41,6 @@
41
41
  <edit-form-buttons
42
42
  :changed="changed"
43
43
  :valid="valid"
44
- small
45
44
  angular
46
45
  :has="{reset: !!modelToEdit.id}"
47
46
  @save="$emit('save', modelToEdit, ignoreChangesOnClose, close)"
@@ -8,6 +8,7 @@
8
8
  @keydown.tab.prevent.exact="keyenter"
9
9
  @keydown.space.prevent="keyenter"
10
10
  @keydown.tab.shift.prevent="$emit('backtab')"
11
+ @keydown.exact="keyinput"
11
12
  >
12
13
  <template v-if="models_.length">
13
14
  <a-table v-bind="$attrs">
@@ -56,6 +57,7 @@ import { ListViewMixin } from '@a-vue/components/list/ListViewMixin'
56
57
  })
57
58
  export default class SearchSelectList extends Mixins(ListViewMixin) {
58
59
  activeModelIndex = -1
60
+ localSearchKey = ''
59
61
 
60
62
  get hasHeader () {
61
63
  return this.$slots.header && this.$slots.header.length > 1
@@ -88,6 +90,19 @@ export default class SearchSelectList extends Mixins(ListViewMixin) {
88
90
  }
89
91
  }
90
92
 
93
+ keyinput (event) {
94
+ if (event.key.length !== 1) {
95
+ return
96
+ }
97
+
98
+ this.localSearchKey = event.key
99
+ this.activeModelIndex = this.findActiveIndexForLocalSearch()
100
+
101
+ setTimeout(() => {
102
+ this.localSearchKey = ''
103
+ }, 200)
104
+ }
105
+
91
106
  keydown () {
92
107
  this.activeModelIndex++
93
108
  if (this.activeModelIndex > this.models_.length - 1) {
@@ -131,6 +146,19 @@ export default class SearchSelectList extends Mixins(ListViewMixin) {
131
146
  }
132
147
  return -1
133
148
  }
149
+
150
+ findActiveIndexForLocalSearch () {
151
+ for (const [index, model] of this.models_.entries()) {
152
+ const regex = new RegExp(`^${this.localSearchKey}`, 'i')
153
+ if (model.getTitle().match(regex)) {
154
+ if (this.activeModelIndex === index) {
155
+ continue
156
+ }
157
+ return index
158
+ }
159
+ }
160
+ return this.activeModelIndex
161
+ }
134
162
  }
135
163
  </script>
136
164