@afeefa/vue-app 0.0.110 → 0.0.111

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.110
1
+ 0.0.111
package/README.md CHANGED
@@ -1,3 +1,7 @@
1
1
  # @afeefa/vue-app
2
2
 
3
3
  Force push :-);
4
+
5
+ ## Documentation
6
+
7
+ * [Components / AModal](docs/components/amodal.md)
@@ -0,0 +1,19 @@
1
+ # AModal
2
+
3
+ It's a Modal capsule for vuetifys Modals. Whats the magic about it?
4
+
5
+ The Modal is by default relatively positioned near its activator.
6
+
7
+ ## Properties
8
+
9
+ * show
10
+ * icon
11
+ * title
12
+ * beforeClose
13
+ * anchorPosition
14
+ * screenCentered
15
+
16
+
17
+ ### screenCentered
18
+
19
+ if set to true the Modal is positioned in the middle of the Viewport, like in vuetify's default beavior.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afeefa/vue-app",
3
- "version": "0.0.110",
3
+ "version": "0.0.111",
4
4
  "description": "",
5
5
  "author": "Afeefa Kollektiv <kollektiv@afeefa.de>",
6
6
  "license": "MIT",
@@ -10,6 +10,7 @@ export class ApiAction extends ApiResourcesApiAction {
10
10
  _dispatchGlobalSaveEvents = false
11
11
  _minDuration = 100
12
12
  _startTime = 0
13
+ _showError = true
13
14
 
14
15
  id (id) {
15
16
  this.param('id', id)
@@ -21,6 +22,11 @@ export class ApiAction extends ApiResourcesApiAction {
21
22
  return this
22
23
  }
23
24
 
25
+ hideError () {
26
+ this._showError = false
27
+ return this
28
+ }
29
+
24
30
  dispatchGlobalLoadingEvents (dispatch = true) {
25
31
  this._dispatchGlobalLoadingEvents = dispatch
26
32
  return this
@@ -27,10 +27,12 @@ export class DeleteAction extends ApiAction {
27
27
  }
28
28
 
29
29
  processError (result) {
30
- eventBus.dispatch(new AlertEvent(AlertEvent.ERROR, {
31
- headline: 'Die Daten konnten nicht gelöscht werden.',
32
- message: result.message,
33
- detail: result.detail
34
- }))
30
+ if (this._showError) {
31
+ eventBus.dispatch(new AlertEvent(AlertEvent.ERROR, {
32
+ headline: 'Die Daten konnten nicht gelöscht werden.',
33
+ message: result.message,
34
+ detail: result.detail
35
+ }))
36
+ }
35
37
  }
36
38
  }
@@ -9,10 +9,12 @@ export class GetAction extends ApiAction {
9
9
  }
10
10
 
11
11
  processError (result) {
12
- eventBus.dispatch(new AlertEvent(AlertEvent.ERROR, {
13
- headline: 'Die Daten konntent nicht geladen werden.',
14
- message: result.message,
15
- detail: result.detail
16
- }))
12
+ if (this._showError) {
13
+ eventBus.dispatch(new AlertEvent(AlertEvent.ERROR, {
14
+ headline: 'Die Daten konntent nicht geladen werden.',
15
+ message: result.message,
16
+ detail: result.detail
17
+ }))
18
+ }
17
19
  }
18
20
  }
@@ -17,10 +17,12 @@ export class SaveAction extends ApiAction {
17
17
  }
18
18
 
19
19
  processError (result) {
20
- eventBus.dispatch(new AlertEvent(AlertEvent.ERROR, {
21
- headline: 'Die Daten konnten nicht gespeichert werden.',
22
- message: result.message,
23
- detail: result.detail
24
- }))
20
+ if (this._showError) {
21
+ eventBus.dispatch(new AlertEvent(AlertEvent.ERROR, {
22
+ headline: 'Die Daten konnten nicht gespeichert werden.',
23
+ message: result.message,
24
+ detail: result.detail
25
+ }))
26
+ }
25
27
  }
26
28
  }
@@ -11,7 +11,7 @@
11
11
  no-filter
12
12
 
13
13
  :rules="validationRules"
14
- v-bind="$attrs"
14
+ v-bind="{...$attrs, dense, outlined}"
15
15
  v-on="$listeners"
16
16
  />
17
17
  </template>
@@ -23,7 +23,7 @@ import { Model } from '@afeefa/api-resources-client'
23
23
  import { debounce } from '@a-vue/utils/debounce'
24
24
 
25
25
  @Component({
26
- props: ['items', 'validator', 'defaultValue', 'selectedItemText', 'debounce']
26
+ props: ['items', 'validator', 'defaultValue', 'selectedItemText', 'debounce', {dense: true, outlined: true}]
27
27
  })
28
28
  export default class AAutocomplete extends Vue {
29
29
  isLoading = false
@@ -12,7 +12,7 @@
12
12
  :value="formattedDate"
13
13
  :label="label"
14
14
  :style="cwm_widthStyle"
15
- v-bind="{...$attrs, ...attrs}"
15
+ v-bind="{...$attrs, ...attrs, dense, outlined}"
16
16
  :rules="validationRules"
17
17
  :error-messages="errorMessages"
18
18
  :readonly="type === 'month'"
@@ -41,7 +41,7 @@ import { formatDate } from '@a-vue/utils/format-date'
41
41
  import { ComponentWidthMixin } from './mixins/ComponentWidthMixin'
42
42
 
43
43
  @Component({
44
- props: ['value', 'validator', 'type']
44
+ props: ['value', 'validator', 'type', {dense: true, outlined: true}]
45
45
  })
46
46
  export default class ADatePicker extends Mixins(ComponentWidthMixin) {
47
47
  value_ = null
@@ -153,6 +153,7 @@ export default class ADatePicker extends Mixins(ComponentWidthMixin) {
153
153
  <style lang="scss" scoped>
154
154
  :deep(.v-select__slot) {
155
155
  cursor: pointer;
156
+
156
157
  input {
157
158
  cursor: pointer;
158
159
  }
@@ -1,5 +1,8 @@
1
1
  <template>
2
- <div :class="['a-grid', colsClass, gapClass, evenClass, breakMobileClass]">
2
+ <div
3
+ :style="{display: displayStyle}"
4
+ :class="['a-grid', colsClass, gapClass, evenClass, breakMobileClass]"
5
+ >
3
6
  <slot />
4
7
  </div>
5
8
  </template>
@@ -10,7 +13,7 @@ import { Component, Mixins } from '@a-vue'
10
13
  import { ComponentWidthMixin } from './mixins/ComponentWidthMixin'
11
14
 
12
15
  @Component({
13
- props: ['gap', 'hGap', 'vGap', 'cols', {even: false}, 'breakMobile']
16
+ props: ['gap', 'hGap', 'vGap', 'cols', {inline: false, even: false}, 'breakMobile']
14
17
  })
15
18
  export default class AGrid extends Mixins(ComponentWidthMixin) {
16
19
  get breakMobileClass () {
@@ -19,6 +22,10 @@ export default class AGrid extends Mixins(ComponentWidthMixin) {
19
22
  }
20
23
  }
21
24
 
25
+ get displayStyle () {
26
+ return this.inline ? 'inline-grid' : 'grid'
27
+ }
28
+
22
29
  get colsClass () {
23
30
  const cols = this.cols || 2
24
31
  return 'cols-' + cols
@@ -53,11 +60,10 @@ export default class AGrid extends Mixins(ComponentWidthMixin) {
53
60
 
54
61
  <style scoped lang="scss">
55
62
  .a-grid {
56
- display: grid;
57
-
58
63
  @for $i from 1 through 8 {
59
64
  &.cols-#{$i} {
60
65
  grid-template-columns: repeat(#{$i}, auto);
66
+
61
67
  &.even {
62
68
  grid-template-columns: repeat(#{$i}, 1fr);
63
69
  }
@@ -67,6 +73,7 @@ export default class AGrid extends Mixins(ComponentWidthMixin) {
67
73
  &.breakMobile {
68
74
  @media (max-width: 900px), (orientation : portrait) {
69
75
  grid-template-columns: 1fr;
76
+
70
77
  &.even {
71
78
  grid-template-columns: 1fr;
72
79
  }
@@ -5,7 +5,7 @@
5
5
  :items="items_"
6
6
  :valueComparator="compareValues"
7
7
  :style="cwm_widthStyle"
8
- v-bind="$attrs"
8
+ v-bind="{...$attrs, dense, outlined}"
9
9
  v-on="$listeners"
10
10
  />
11
11
  </template>
@@ -17,7 +17,7 @@ import { Model } from '@afeefa/api-resources-client'
17
17
  import { ComponentWidthMixin } from './mixins/ComponentWidthMixin'
18
18
 
19
19
  @Component({
20
- props: ['validator', 'defaultValue', 'items']
20
+ props: ['validator', 'defaultValue', 'items', {dense: true, outlined: true}]
21
21
  })
22
22
  export default class ASelect extends Mixins(ComponentWidthMixin) {
23
23
  items_ = []
@@ -96,3 +96,10 @@ export default class ASelect extends Mixins(ComponentWidthMixin) {
96
96
  }
97
97
  }
98
98
  </script>
99
+
100
+
101
+ <style lang="scss" scoped>
102
+ .v-text-field :deep(.v-input__icon--clear) { // always show clear icon, https://github.com/vuetifyjs/vuetify/pull/15876
103
+ opacity: 1;
104
+ }
105
+ </style>
@@ -3,7 +3,7 @@
3
3
  ref="input"
4
4
  :rules="validationRules"
5
5
  :counter="counter"
6
- v-bind="$attrs"
6
+ v-bind="{...$attrs, dense, outlined}"
7
7
  v-on="$listeners"
8
8
  />
9
9
  </template>
@@ -13,7 +13,7 @@
13
13
  import { Component, Vue } from '@a-vue'
14
14
 
15
15
  @Component({
16
- props: ['validator']
16
+ props: ['validator', {dense: true, outlined: true}]
17
17
  })
18
18
  export default class ATextArea extends Vue {
19
19
  mounted () {
@@ -1,45 +1,142 @@
1
1
  <template>
2
2
  <v-text-field
3
3
  ref="input"
4
- :type="type"
5
- :autocomplete="autocomplete"
6
- :rules="validationRules"
4
+ v-model="internalValue"
7
5
  :counter="counter"
8
6
  :style="cwm_widthStyle"
9
- v-bind="$attrs"
10
- v-on="$listeners"
11
- @click:append="showPassword = !showPassword"
7
+ :error-messages="errorMessages"
8
+ v-bind="attrs"
9
+ @input="inputChanged"
10
+ @keyup.esc="clear"
11
+ @click:clear="clear"
12
+ v-on="listeners"
12
13
  />
13
14
  </template>
14
15
 
15
16
 
16
17
  <script>
17
- import { Component, Watch, Mixins } from '@a-vue'
18
+ import { Component, Watch, Mixins, Inject } from '@a-vue'
18
19
  import { debounce } from '@a-vue/utils/debounce'
19
20
  import { ComponentWidthMixin } from './mixins/ComponentWidthMixin'
20
21
 
21
22
  @Component({
22
- props: ['debounce', 'validator', {focus: false, password: false, number: false}]
23
+ props: ['value', 'debounce', 'validator', 'rules', {dense: true, outlined: true, focus: false, number: false}]
23
24
  })
24
25
  export default class ATextField extends Mixins(ComponentWidthMixin) {
25
26
  $hasOptions = ['counter']
26
27
 
27
- showPassword = false
28
+ @Inject({ from: 'form', default: null }) form
29
+
30
+ internalValue = null
31
+ errorMessages = []
32
+ debounceInputFunction = null
28
33
 
29
34
  created () {
30
- if (this.debounce) {
31
- this.$listeners.input = debounce(value => {
32
- this.$emit('input', value)
33
- }, this.debounce, value => value)
34
- }
35
+ this.form && this.form.register(this)
36
+
37
+ this.setInternalValue(this.value)
35
38
  }
36
39
 
37
40
  mounted () {
38
41
  this.setFocus()
39
42
 
40
- if (this.validator) {
41
- this.$refs.input.validate(true)
43
+ this.$emit('input:internal', this.internalValue)
44
+ this.validate()
45
+ }
46
+
47
+ setInternalValue (value) {
48
+ if (typeof value === 'number') {
49
+ value = value.toString()
42
50
  }
51
+ this.internalValue = value || ''
52
+ }
53
+
54
+ @Watch('value')
55
+ valueChanged () {
56
+ this.setInternalValue(this.value)
57
+ }
58
+
59
+ get listeners () {
60
+ // remove input from nested listening
61
+ // let clients listend to only THIS component
62
+ const listeners = {...this.$listeners}
63
+ delete listeners.input
64
+ return listeners
65
+ }
66
+
67
+ get attrs () {
68
+ // remove 'rules' from being passed to v-text-field
69
+ const attrs = {...this.$attrs}
70
+ delete attrs.rules
71
+
72
+ attrs.dense = this.dense
73
+ attrs.outlined = this.outlined
74
+ return attrs
75
+ }
76
+
77
+ clear () {
78
+ if (this.$attrs.clearable || this.escClearable) {
79
+ this.setInternalValue('')
80
+ this.validate()
81
+ this.$emit('input', this.emptyValue)
82
+ }
83
+ }
84
+
85
+ inputChanged () {
86
+ this.$emit('input:internal', this.internalValue)
87
+
88
+ const valid = this.validate()
89
+ if (!valid) {
90
+ return
91
+ }
92
+
93
+ const value = this.stringToNumber(this.internalValue)
94
+
95
+ if (this.debounce) {
96
+ if (!this.debounceInputFunction) {
97
+ this.debounceInputFunction = debounce(value => {
98
+ this.$emit('input', value)
99
+ }, this.debounce, value => value) // fire immediately if !value (click clearable-x)
100
+ }
101
+ this.debounceInputFunction(value)
102
+ } else {
103
+ this.$emit('input', value)
104
+ }
105
+ }
106
+
107
+ stringToNumber (value) {
108
+ if (this.treatAsNumericValue) {
109
+ if (!value) {
110
+ value = null
111
+ } else {
112
+ value = this.internalValue.match(/^\d*(.\d+)?$/) ? parseFloat(this.internalValue) : NaN
113
+ }
114
+ }
115
+ return value
116
+ }
117
+
118
+ get type () {
119
+ return this.$attrs.type || 'text'
120
+ }
121
+
122
+ get treatAsNumericValue () {
123
+ return this.type === 'number' || this.number
124
+ }
125
+
126
+ validate () {
127
+ const rules = this.validationRules
128
+ let errorMessage = null
129
+ for (const rule of rules) {
130
+ const value = this.stringToNumber(this.internalValue)
131
+ const result = rule(value)
132
+ if (result !== true) {
133
+ errorMessage = result
134
+ break
135
+ }
136
+ }
137
+
138
+ this.errorMessages = [errorMessage].filter(e => e)
139
+ return !this.errorMessages.length
43
140
  }
44
141
 
45
142
  @Watch('focus')
@@ -58,49 +155,42 @@ export default class ATextField extends Mixins(ComponentWidthMixin) {
58
155
  }
59
156
  }
60
157
 
61
- get type () {
62
- if (this.password && !this.showPassword) {
63
- return 'password'
64
- }
65
- return 'text'
66
- }
67
-
68
- get appendIcon () {
69
- if (this.password) {
70
- return this.showPassword ? '$eyeIcon' : '$eyeOffIcon'
71
- }
72
- return null
73
- }
74
-
75
- get autocomplete () {
76
- if (this.password) {
77
- return 'new-password'
78
- }
79
- return null
80
- }
81
-
82
158
  get validationRules () {
159
+ if (this.$attrs.rules) {
160
+ return this.$attrs.rules
161
+ }
83
162
  const label = this.$attrs.label
84
163
  return (this.validator && this.validator.getRules(label)) || []
85
164
  }
86
165
 
87
166
  get counter () {
88
167
  if (!this.$has.counter) {
89
- return false
168
+ return null
90
169
  }
91
170
 
92
171
  if (!this.validator) {
93
- return false
172
+ return null
94
173
  }
95
174
 
96
- return (!this.number && this.validator.getParams().max) || false
175
+ return this.validator.getMaxValueLength()
176
+ }
177
+
178
+ get emptyValue () {
179
+ if (this.validator) {
180
+ return this.validator.getEmptyValue()
181
+ }
182
+ return null
97
183
  }
98
184
  }
99
185
  </script>
100
186
 
101
187
 
102
188
  <style lang="scss" scoped>
103
- .v-input:not(.v-input--is-focused) :deep(.v-counter) {
189
+ .v-input:not(.v-input--is-focused) :deep(.v-counter) { // hide counter when not focused
104
190
  display: none;
105
191
  }
192
+
193
+ .v-text-field :deep(.v-input__icon--clear) { // always show clear icon, https://github.com/vuetifyjs/vuetify/pull/15876
194
+ opacity: 1;
195
+ }
106
196
  </style>
@@ -1,12 +1,10 @@
1
1
  <template>
2
2
  <a-text-field
3
- :value="internalValue"
3
+ v-model="model[name]"
4
4
  :label="label || name"
5
5
  :validator="validator"
6
6
  v-bind="$attrs"
7
7
  v-on="$listeners"
8
- @input="textFieldValueChanged"
9
- @blur="onBlur"
10
8
  />
11
9
  </template>
12
10
 
@@ -14,71 +12,7 @@
14
12
  import { Component, Mixins } from '@a-vue'
15
13
  import { FormFieldMixin } from '../FormFieldMixin'
16
14
 
17
- @Component({
18
- props: [{emptyNull: false}]
19
- })
15
+ @Component
20
16
  export default class FormFieldText extends Mixins(FormFieldMixin) {
21
- internalValue = ''
22
-
23
- created () {
24
- this.setInternalValue(this.model[this.name])
25
- this.$watch(() => this.model[this.name], value => {
26
- this.setInternalValue(value)
27
- })
28
- }
29
-
30
- onBlur () {
31
- this.setInternalValue(this.model[this.name], true)
32
- }
33
-
34
- textFieldValueChanged (value) {
35
- this.internalValue = value
36
-
37
- // cast to number
38
- if (this.isNumber) {
39
- value = Number(value)
40
- if (isNaN(value)) {
41
- return // do not set anything to the model
42
- }
43
- }
44
-
45
- // set model value to null if empty
46
- if (this.emptyNull) {
47
- if (this.isNumber) {
48
- if (value === 0) {
49
- value = null
50
- }
51
- } else {
52
- if (!value) {
53
- value = null
54
- }
55
- }
56
- }
57
-
58
- this.model[this.name] = value
59
- this.$emit('input', value)
60
- }
61
-
62
- setInternalValue (value, reset = false) {
63
- if (this.isNumber) {
64
- // reset text field if value is null but keep leading 0 (allows for copy and paste)
65
- if (value === null) {
66
- if (!reset && this.internalValue === '0') {
67
- value = '0'
68
- } else {
69
- value = ''
70
- }
71
- }
72
- } else { // null string should be ''
73
- if (!value) {
74
- value = ''
75
- }
76
- }
77
- this.internalValue = value
78
- }
79
-
80
- get isNumber () {
81
- return this.$attrs.number === ''
82
- }
83
17
  }
84
18
  </script>
@@ -15,7 +15,6 @@ import NestedEditForm from './form/NestedEditForm'
15
15
  import ListFilterPage from './list/filters/ListFilterPage'
16
16
  import ListFilterSearch from './list/filters/ListFilterSearch'
17
17
  import ListFilterSelect from './list/filters/ListFilterSelect'
18
- import ListFilterRow from './list/ListFilterRow'
19
18
 
20
19
  Vue.component('EditForm', EditForm)
21
20
  Vue.component('NestedEditForm', NestedEditForm)
@@ -31,5 +30,4 @@ Vue.component('FormFieldSelect', FormFieldSelect)
31
30
  Vue.component('FormFieldSelect2', FormFieldSelect2)
32
31
  Vue.component('ListFilterPage', ListFilterPage)
33
32
  Vue.component('ListFilterSearch', ListFilterSearch)
34
- Vue.component('ListFilterRow', ListFilterRow)
35
33
  Vue.component('ListFilterSelect', ListFilterSelect)
@@ -1,7 +1,5 @@
1
1
  <template>
2
- <a-row
3
- gap="8"
4
- >
2
+ <a-row gap="8">
5
3
  <a-pagination
6
4
  v-if="count && numPages > 1"
7
5
  v-model="filter.value"
@@ -20,6 +18,7 @@
20
18
  :items="pageSizeFilter.options"
21
19
  :defaultValue="pageSizeFilter.defaultValue"
22
20
  :clearable="!pageSizeFilter.hasDefaultValueSet()"
21
+ hide-details
23
22
  />
24
23
  </a-row>
25
24
  </template>
@@ -54,7 +53,7 @@ export default class ListFilterPage extends Mixins(ListFilterMixin) {
54
53
 
55
54
  <style lang="scss" scoped>
56
55
  .v-select {
57
- max-width: 100px;
56
+ max-width: 150px;
58
57
  }
59
58
 
60
59
  .pageNumber {
@@ -6,6 +6,7 @@
6
6
  :debounce="500"
7
7
  v-bind="$attrs"
8
8
  clearable
9
+ hide-details
9
10
  @keyup.esc="clearValue"
10
11
  />
11
12
  </template>
@@ -7,6 +7,7 @@
7
7
  itemValue="itemValue"
8
8
  :clearable="filter.value !== filter.defaultValue"
9
9
  :defaultValue="filter.defaultValue"
10
+ hide-details
10
11
  v-bind="$attrs"
11
12
  />
12
13
  </template>
package/src/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import './styles/vue-app.scss'
2
2
 
3
- export { Mixins, Vue, Watch } from 'vue-property-decorator'
3
+ export { Mixins, Vue, Watch, Inject, Provide } from 'vue-property-decorator'
4
4
  export { Component } from '@a-vue/components/vue/Component'
@@ -76,7 +76,7 @@
76
76
  </template>
77
77
 
78
78
  <template #filters>
79
- <list-filter-row>
79
+ <a-row gap="4">
80
80
  <list-filter-search
81
81
  :focus="true"
82
82
  maxWidth="100%"
@@ -87,7 +87,7 @@
87
87
  :has="{page_size: false, page_number: true}"
88
88
  :totalVisible="0"
89
89
  />
90
- </list-filter-row>
90
+ </a-row>
91
91
  </template>
92
92
 
93
93
  <template #row="{ model, on }">
@@ -1,26 +0,0 @@
1
- <template>
2
- <a-row
3
- :gap="gap || 4"
4
- class="mb-0"
5
- >
6
- <slot />
7
- </a-row>
8
- </template>
9
-
10
-
11
- <script>
12
- import { Component, Vue } from '@a-vue'
13
-
14
- @Component({
15
- props: ['gap']
16
- })
17
- export default class ListFilterRow extends Vue {
18
- }
19
- </script>
20
-
21
-
22
- <style scoped lang="scss">
23
- :deep(.v-text-field__details) {
24
- display: none;
25
- }
26
- </style>