@afeefa/vue-app 0.0.194 → 0.0.196

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.194
1
+ 0.0.196
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afeefa/vue-app",
3
- "version": "0.0.194",
3
+ "version": "0.0.196",
4
4
  "description": "",
5
5
  "author": "Afeefa Kollektiv <kollektiv@afeefa.de>",
6
6
  "license": "MIT",
@@ -0,0 +1,112 @@
1
+ <template>
2
+ <div :class="['a-category', {dark: theme === 'dark'}]">
3
+ <div
4
+ class="border"
5
+ :style="{background: borderColor}"
6
+ />
7
+
8
+ <div
9
+ :class="['content', {hasRemove: $has.remove}]"
10
+ :title="category.title"
11
+ >
12
+ <div
13
+ v-for="parentCategory in parentCategories"
14
+ :key="parentCategory.id"
15
+ class="parent"
16
+ >
17
+ {{ parentCategory.title }}
18
+ </div>
19
+
20
+ <div>{{ category.title }}</div>
21
+ </div>
22
+
23
+ <div
24
+ v-if="$has.remove"
25
+ class="remove"
26
+ >
27
+ <a-icon
28
+ small
29
+ class="iconButton"
30
+ @click="$emit('remove')"
31
+ >
32
+ $closeCircleIcon
33
+ </a-icon>
34
+ </div>
35
+ </div>
36
+ </template>
37
+
38
+ <script>
39
+ import { Component, Vue } from '@a-vue'
40
+
41
+ @Component({
42
+ props: ['category', 'theme', 'color']
43
+ })
44
+ export default class ACategory extends Vue {
45
+ $hasOptions = [{remove: false}]
46
+
47
+ get borderColor () {
48
+ return this.color || this.category.color || 'gray'
49
+ }
50
+
51
+ get parentCategories () {
52
+ const parents = []
53
+ let parent = this.category.parent
54
+ while (parent) {
55
+ parents.unshift(parent)
56
+ parent = parent.parent
57
+ }
58
+ return parents
59
+ }
60
+ }
61
+ </script>
62
+
63
+ <style lang="scss" scoped>
64
+ .a-category {
65
+ background: white;
66
+ white-space: nowrap;
67
+ font-size: 1rem;
68
+ display: flex;
69
+ align-items: stretch;
70
+ padding-right: .2rem;
71
+
72
+ &.dark {
73
+ background: #F6F6F6;
74
+ }
75
+
76
+ .border {
77
+ flex: 0 0 5px;
78
+ }
79
+
80
+ .content {
81
+ padding: .4rem .5rem;
82
+ display: flex;
83
+ justify-content: space-around;
84
+ flex-direction: column;
85
+ line-height: 1;
86
+ color: rgba(0, 0, 0, .87);
87
+
88
+ max-width: 200px;
89
+
90
+ > * {
91
+ overflow: hidden;
92
+ text-overflow: ellipsis;
93
+ }
94
+
95
+ .parent {
96
+ font-size: .8rem;
97
+ color: #999999;
98
+ line-height: 1;
99
+ }
100
+
101
+ &.hasRemove {
102
+ padding-right: .5rem;
103
+ }
104
+ }
105
+
106
+ .remove {
107
+ align-self: center;
108
+ margin-top: -.1rem;
109
+ padding-right: .6rem;
110
+ }
111
+ }
112
+ </style>
@@ -0,0 +1,140 @@
1
+ <template>
2
+ <div class="popup">
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <script>
8
+ import { Component, Mixins, Watch } from '@a-vue'
9
+ import { CancelOnEscMixin } from '@a-vue/services/escape/CancelOnEscMixin'
10
+ import { UsesPositionServiceMixin } from '../services/position/UsesPositionServiceMixin'
11
+ import { positionService, PositionConfig } from '../services/PositionService'
12
+
13
+ @Component({
14
+ props: ['position']
15
+ })
16
+ export default class APopup extends Mixins(CancelOnEscMixin, UsesPositionServiceMixin) {
17
+ popupTrigger = null
18
+ currentPosition = null
19
+
20
+ created () {
21
+ window.addEventListener('mousedown', this.onClickOutside)
22
+ window.addEventListener('wheel', this.onScroll, {passive: false})
23
+ window.addEventListener('touchmove', this.onScroll)
24
+ window.addEventListener('keydown', this.onScroll)
25
+
26
+ this.currentPosition = (this.position || new PositionConfig())
27
+ .setTarget(this)
28
+ .onAnchorEl(anchorEl => {
29
+ this.popupTrigger = anchorEl
30
+ })
31
+
32
+ this.urp_registerPositionWatcher(this.currentPosition)
33
+
34
+ this.coe_watchCancel()
35
+ }
36
+
37
+ mounted () {
38
+ this.getContainer().appendChild(this.$el)
39
+ }
40
+
41
+ destroyed () {
42
+ window.removeEventListener('mousedown', this.onClickOutside)
43
+ window.removeEventListener('wheel', this.onScroll)
44
+ window.removeEventListener('touchmove', this.onScroll)
45
+ window.removeEventListener('keydown', this.onScroll)
46
+
47
+ const container = this.getContainer()
48
+ if (container.contains(this.$el)) {
49
+ container.removeChild(this.$el)
50
+ }
51
+ }
52
+
53
+ @Watch('position')
54
+ positionChanged () {
55
+ this.currentPosition.update(this.position)
56
+ }
57
+
58
+ getContainer () {
59
+ return document.getElementById('popupContainer')
60
+ }
61
+
62
+ coe_cancelOnEsc () {
63
+ this.close()
64
+ return false
65
+ }
66
+
67
+ onScroll (event) {
68
+ // ignore esc event
69
+ if (event instanceof KeyboardEvent) {
70
+ const key = event.key || event.keyCode
71
+ if (key === 'Escape' || key === 'Esc' || key === 27) {
72
+ return
73
+ }
74
+ }
75
+
76
+ // ignore child popup scroll / event
77
+ if (this.targetIsIncludedPopup(event.target)) {
78
+ return
79
+ }
80
+
81
+ if (this.$el.contains(event.target)) {
82
+ if (!(event instanceof KeyboardEvent)) { // no keydown, must be wheel/touchmove
83
+ const scrollParent = positionService.getScrollParent(event.target, true)
84
+ if (!this.$el.contains(scrollParent)) { // do not scroll at all if target is not scrollable
85
+ event.preventDefault()
86
+ }
87
+ }
88
+ return
89
+ }
90
+
91
+ this.close()
92
+ }
93
+
94
+ targetIsIncludedPopup (target) {
95
+ const popups = this.getContainer().children
96
+ let foundMyself = false
97
+ for (const popup of popups) {
98
+ if (foundMyself) {
99
+ if (popup.contains(target)) {
100
+ return true
101
+ }
102
+ }
103
+ foundMyself = popup === this.$el
104
+ }
105
+ return false
106
+ }
107
+
108
+ close () {
109
+ this.$emit('close')
110
+ }
111
+
112
+ onClickOutside (e) {
113
+ // popup clicked
114
+ if (this.$el.contains(e.target)) {
115
+ return
116
+ }
117
+
118
+ // trigger clicked
119
+ if (this.popupTrigger.contains(e.target)) {
120
+ return
121
+ }
122
+
123
+ // check included popup clicked
124
+ if (this.targetIsIncludedPopup(e.target)) {
125
+ return
126
+ }
127
+
128
+ this.close()
129
+ }
130
+ }
131
+ </script>
132
+
133
+ <style lang="scss" scoped>
134
+ .popup {
135
+ position: absolute;
136
+ background-color: white;
137
+ padding: 1rem;
138
+ box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .2), 0 2px 10px 0 rgba(0, 0, 0, .2);
139
+ }
140
+ </style>
@@ -0,0 +1,77 @@
1
+ <template>
2
+ <div class="popupMenu">
3
+ <div
4
+ class="trigger"
5
+ @click="open"
6
+ >
7
+ <slot />
8
+ </div>
9
+
10
+ <popup-menu-list
11
+ v-if="visible"
12
+ :items="items"
13
+ :position="position"
14
+ v-bind="$attrs"
15
+ @close="close"
16
+ @click="click"
17
+ >
18
+ <template #item="{ item }">
19
+ <component
20
+ :is="itemRenderer || ItemRenderer"
21
+ :item="item"
22
+ />
23
+ </template>
24
+ </popup-menu-list>
25
+ </div>
26
+ </template>
27
+
28
+
29
+ <script>
30
+ import { PositionConfig } from '../services/PositionService'
31
+ import { Component, Vue } from '@a-vue'
32
+ import PopupMenuList from './popup-menu/PopupMenuList'
33
+ import ItemRenderer from './popup-menu/ItemRenderer'
34
+
35
+ @Component({
36
+ props: ['items', 'itemRenderer'],
37
+ components: {
38
+ PopupMenuList
39
+ }
40
+ })
41
+ export default class APopupMenu extends Vue {
42
+ visible = false
43
+ ItemRenderer = ItemRenderer
44
+
45
+ open () {
46
+ this.visible = true
47
+ }
48
+
49
+ click (item) {
50
+ this.$emit('select', item)
51
+ this.close()
52
+ }
53
+
54
+ close () {
55
+ this.visible = false
56
+ }
57
+
58
+ get position () {
59
+ const position = new PositionConfig()
60
+ .setAnchor(this, '.trigger')
61
+ .targetLeft().anchorRight()
62
+ .targetMiddle().anchorMiddle()
63
+ return position
64
+ }
65
+ }
66
+ </script>
67
+
68
+
69
+ <style lang="scss" scoped>
70
+ #popupContainer .selectPopup {
71
+ padding: .5rem;
72
+ }
73
+
74
+ .trigger {
75
+ display: inline-block;
76
+ }
77
+ </style>
@@ -2,4 +2,5 @@ import { BaseEvent } from '@a-vue/plugins/event-bus/BaseEvent'
2
2
 
3
3
  export class FlyingContextEvent extends BaseEvent {
4
4
  static HIDE_ALL = 'FlyingContextEvent:hide-all'
5
+ static START_HIDE_CONTEXT = 'FlyingContextEvent:start-hide-context'
5
6
  }
@@ -19,6 +19,7 @@
19
19
 
20
20
  <script>
21
21
  import { Component, Vue, Watch } from '@a-vue'
22
+ // import { getDiff } from 'json-difference'
22
23
 
23
24
  @Component({
24
25
  props: [
@@ -102,6 +103,8 @@ export default class EditForm extends Vue {
102
103
  }
103
104
  // console.log(this.lastJson)
104
105
  // console.log(this.json)
106
+ // console.log(JSON.stringify(getDiff(this.lastJson, this.json)))
107
+
105
108
  return this.json !== this.lastJson
106
109
  }
107
110
 
@@ -0,0 +1,194 @@
1
+ <template>
2
+ <div v-if="categories_">
3
+ <v-input
4
+ ref="validationInput"
5
+ :value="model[name]"
6
+ :rules="validationRules"
7
+ hide-details="auto"
8
+ >
9
+ <div>
10
+ <v-label v-if="false">
11
+ <a-row gap="2">
12
+ <strong v-if="false">{{ label }}</strong>
13
+
14
+ <a-popup-menu
15
+ v-if="selectedCategories.length && isMultiple"
16
+ :items="categories"
17
+ :canSelectParent="false"
18
+ @select="add"
19
+ >
20
+ <a-icon
21
+ size="1.3rem"
22
+ class="contextButton mt-n1"
23
+ title="Bearbeiten"
24
+ >
25
+ $addIcon
26
+ </a-icon>
27
+ </a-popup-menu>
28
+ </a-row>
29
+ </v-label>
30
+
31
+ <div
32
+ v-if="selectedCategories.length"
33
+ class="categories"
34
+ >
35
+ <a-category
36
+ v-for="category in selectedCategories"
37
+ :key="category.id"
38
+ :category="{...category, parent: {title: label}}"
39
+ theme="dark"
40
+ :has="{remove: true}"
41
+ :color="color"
42
+ @remove="remove(category)"
43
+ />
44
+ </div>
45
+
46
+ <a-popup-menu
47
+ v-if="!selectedCategories.length"
48
+ :items="categories_"
49
+ :canSelectParent="false"
50
+ class="mt-2"
51
+ @select="add"
52
+ >
53
+ <a-icon-button
54
+ small
55
+ icon="$plusIcon"
56
+ :text="label"
57
+ />
58
+ </a-popup-menu>
59
+
60
+ <a-popup-menu
61
+ v-else-if="isMultiple && selectedCategories.length < maxItems"
62
+ :items="categories_"
63
+ :canSelectParent="false"
64
+ class="mt-1"
65
+ @select="add"
66
+ >
67
+ <a>{{ label }} hinzufügen</a>
68
+ </a-popup-menu>
69
+ </div>
70
+ </v-input>
71
+
72
+ <div
73
+ v-if="errorMessages.length"
74
+ class="mt-4 error--text"
75
+ >
76
+ {{ errorMessages[0] }}
77
+ </div>
78
+ </div>
79
+ </template>
80
+
81
+ <script>
82
+ import { Component, Mixins } from '@a-vue'
83
+ import { FormFieldMixin } from '../FormFieldMixin'
84
+ import { ListAction } from '@a-vue/api-resources/ApiActions'
85
+
86
+ @Component({
87
+ props: [
88
+ 'color',
89
+ {
90
+ getChildren: {
91
+ default: () => i => i.children || []
92
+ }
93
+ },
94
+ 'categories'
95
+ ]
96
+ })
97
+ export default class FormFieldCategory extends Mixins(FormFieldMixin) {
98
+ categories_ = null
99
+ errorMessages = []
100
+
101
+ async created () {
102
+ if (this.categories) {
103
+ this.initCategories(this.categories)
104
+ } else {
105
+ await this.loadCategories()
106
+ }
107
+
108
+ this.$nextTick(() => { // wait for root v-if
109
+ this.$refs.validationInput.validate(true)
110
+ })
111
+ }
112
+
113
+ mounted () {
114
+
115
+ }
116
+
117
+ add (category) {
118
+ if (this.isMultiple) {
119
+ this.remove(category) // add again at end
120
+ this.model[this.name].push(category)
121
+ } else {
122
+ this.model[this.name] = category
123
+ }
124
+
125
+ this.$emit('add', category)
126
+ }
127
+
128
+ remove (category) {
129
+ if (this.isMultiple) {
130
+ this.model[this.name] = this.selectedCategories.filter(c => c.id !== category.id)
131
+ } else {
132
+ this.model[this.name] = null
133
+ }
134
+
135
+ this.$emit('remove', category)
136
+ }
137
+
138
+ get selectedCategories () {
139
+ if (this.isMultiple) {
140
+ return this.model[this.name]
141
+ } else {
142
+ return [this.model[this.name]].filter(c => c)
143
+ }
144
+ }
145
+
146
+ get isMultiple () {
147
+ return this.field.getRelatedType().isList
148
+ }
149
+
150
+ async loadCategories () {
151
+ const request = this.field.createOptionsRequest()
152
+ const {models} = await ListAction.fromRequest(request).load()
153
+ this.initCategories(models)
154
+ }
155
+
156
+ initCategories (models) {
157
+ this.categories_ = models.map(m => {
158
+ let children = this.getChildren(m).map(c => ({
159
+ ...c,
160
+ color: this.color
161
+ }))
162
+
163
+ if (children.length === 1) {
164
+ m = children[0]
165
+ children = []
166
+ }
167
+
168
+ return {
169
+ ...m,
170
+ color: this.color,
171
+ children
172
+ }
173
+ })
174
+ }
175
+
176
+ get maxItems () {
177
+ return this.validator?.getParam('max') || 100000
178
+ }
179
+ }
180
+
181
+ </script>
182
+
183
+
184
+ <style lang="scss" scoped>
185
+ :deep() .v-messages__message {
186
+ color: #EE5252;
187
+ }
188
+
189
+ .categories {
190
+ display: flex;
191
+ flex-wrap: wrap;
192
+ gap: .5rem;
193
+ }
194
+ </style>
@@ -2,6 +2,7 @@ import Vue from 'vue'
2
2
 
3
3
  import EditForm from './form/EditForm'
4
4
  import EditModal from './form/EditModal'
5
+ import FormFieldCategory from './form/fields/FormFieldCategory'
5
6
  import FormFieldCheckbox from './form/fields/FormFieldCheckbox'
6
7
  import FormFieldDate from './form/fields/FormFieldDate'
7
8
  import FormFieldRadioGroup from './form/fields/FormFieldRadioGroup'
@@ -25,6 +26,7 @@ Vue.component('FormFieldText', FormFieldText)
25
26
  Vue.component('FormFieldTextArea', FormFieldTextArea)
26
27
  Vue.component('FormFieldRichTextArea', FormFieldRichTextArea)
27
28
  Vue.component('FormFieldRadioGroup', FormFieldRadioGroup)
29
+ Vue.component('FormFieldCategory', FormFieldCategory)
28
30
  Vue.component('FormFieldCheckbox', FormFieldCheckbox)
29
31
  Vue.component('FormFieldDate', FormFieldDate)
30
32
  Vue.component('FormFieldTime', FormFieldTime)
@@ -0,0 +1,42 @@
1
+ <template>
2
+ <div class="itemRenderer">
3
+ <div
4
+ class="border"
5
+ :style="{background: color}"
6
+ />
7
+ <div class="content">
8
+ {{ item.title }}
9
+ </div>
10
+ </div>
11
+ </template>
12
+
13
+ <script>
14
+ import { Component, Vue } from '@a-vue'
15
+
16
+ @Component({
17
+ props: ['item']
18
+ })
19
+ export default class ItemRenderer extends Vue {
20
+ get color () {
21
+ return this.item.color || 'gray'
22
+ }
23
+ }
24
+ </script>
25
+
26
+ <style lang="scss" scoped>
27
+ .itemRenderer {
28
+ min-width: 200px;
29
+ line-height: 1.2;
30
+ display: flex;
31
+ font-size: .9rem;
32
+
33
+ .border {
34
+ flex: 0 0 4px;
35
+ margin-right: .5rem;
36
+ }
37
+
38
+ .content {
39
+ padding: .2rem .2rem;
40
+ }
41
+ }
42
+ </style>
@@ -0,0 +1,194 @@
1
+ <template>
2
+ <a-popup
3
+ :class="['popupMenuList', 'level-' + nestLevel]"
4
+ :position="position"
5
+ @close="$emit('close')"
6
+ >
7
+ <template v-if="parent">
8
+ <div
9
+ :class="['item parent', {selected: selectedItem === parent}]"
10
+ @click="click(parent)"
11
+ >
12
+ <div class="content">
13
+ <slot
14
+ name="item"
15
+ :item="parent"
16
+ />
17
+ </div>
18
+ </div>
19
+
20
+ <hr>
21
+ </template>
22
+
23
+ <input
24
+ v-if="items.length > 20"
25
+ v-model="filterTerm"
26
+ type="text"
27
+ class="inputElement inputElement--filter"
28
+ placeholder="Filtern"
29
+ autofocus
30
+ >
31
+
32
+ <div
33
+ v-for="item in filteredItems"
34
+ :key="item.id"
35
+ @click="click(item)"
36
+ >
37
+ <div
38
+ :class="['item', {isFolder: item.children?.length, selected: selectedItem === item}]"
39
+ :data-id="item.id"
40
+ >
41
+ <div class="content">
42
+ <slot
43
+ name="item"
44
+ :item="item"
45
+ />
46
+ </div>
47
+
48
+ <a-icon
49
+ v-if="item.children?.length"
50
+ icon="chevron-right"
51
+ class="more"
52
+ >
53
+ $chevronRightIcon
54
+ </a-icon>
55
+ </div>
56
+
57
+ <popup-menu-list
58
+ v-if="item.children?.length && selectedItem === item"
59
+ :parent="canSelectParent && item"
60
+ :items="item.children"
61
+ :level="nestLevel + 1"
62
+ :position="getPosition(item)"
63
+ @close="closeNested"
64
+ @click="clickNested"
65
+ >
66
+ <template #item="{ item: subItem }">
67
+ <slot
68
+ name="item"
69
+ :item="subItem"
70
+ />
71
+ </template>
72
+ </popup-menu-list>
73
+ </div>
74
+ <p
75
+ v-if="filteredItems.length === 0"
76
+ class="noItems"
77
+ >
78
+ Keine Einträge
79
+ </p>
80
+ </a-popup>
81
+ </template>
82
+
83
+
84
+ <script>
85
+ import { Component, Vue } from '@a-vue'
86
+ import { PositionConfig } from '../../services/PositionService'
87
+
88
+ @Component({
89
+ props: ['items', 'parent', 'level', 'position', {canSelectParent: true}],
90
+ name: 'popup-menu-list'
91
+ })
92
+ export default class PopupMenuList extends Vue {
93
+ selectedItem = null
94
+ filterTerm = ''
95
+
96
+ get nestLevel () {
97
+ return this.level || 0
98
+ }
99
+
100
+ get filteredItems () {
101
+ if (!this.filterTerm) {
102
+ return this.items
103
+ } else {
104
+ return this.items.filter(item => {
105
+ return item.title.toLowerCase().includes(this.filterTerm.toLowerCase())
106
+ })
107
+ }
108
+ }
109
+
110
+ click (item) {
111
+ if (item === this.parent) { // parent item
112
+ this.$emit('click', item)
113
+ } else if (item.children?.length) { // container
114
+ this.openNested(item)
115
+ } else { // single item
116
+ this.$emit('click', item)
117
+ }
118
+ }
119
+
120
+ clickNested (item) {
121
+ this.$emit('click', item)
122
+ }
123
+
124
+ openNested (item) {
125
+ this.selectedItem = item
126
+ }
127
+
128
+ closeNested () {
129
+ this.selectedItem = null
130
+ }
131
+
132
+ getPosition (item) {
133
+ // console.log('getPosition this.' + `.item[data-id="${item.id}"]`)
134
+ const position = new PositionConfig()
135
+ .setAnchor(this, `.item[data-id="${item.id}"]`)
136
+ .targetLeft().anchorRight()
137
+ .targetMiddle().anchorMiddle()
138
+ .diffX('.2rem')
139
+ return position
140
+ }
141
+ }
142
+ </script>
143
+
144
+
145
+ <style lang="scss" scoped>
146
+ #popupContainer .popupMenuList {
147
+ padding: .3rem .5rem;
148
+ max-height: 600px;
149
+ overflow-y: auto;
150
+ }
151
+
152
+ .noItems {
153
+ font-size: .8rem;
154
+ font-weight:bold;
155
+ margin: .5em 0;
156
+ white-space: nowrap;
157
+ }
158
+
159
+ .inputElement--filter {
160
+ font-size: .8rem;
161
+ margin-bottom: .5em;
162
+ padding: .5em;
163
+ }
164
+
165
+ .item {
166
+ display: flex;
167
+ align-items: center;
168
+ margin: .2rem 0;
169
+
170
+ cursor: pointer;
171
+ user-select: none;
172
+
173
+ &:hover,
174
+ &.selected {
175
+ background-color: #ECECEC;
176
+ }
177
+
178
+ &.isFolder {
179
+ color: #666666;
180
+ }
181
+
182
+ .more {
183
+ text-align: right;
184
+ margin-left: 1rem;
185
+ color: #999999;
186
+ font-size: .8rem;
187
+ }
188
+ }
189
+
190
+ hr {
191
+ border: none;
192
+ border-top: 1px solid #DDDDDD;
193
+ }
194
+ </style>
@@ -153,15 +153,33 @@ export default class SearchSelectList extends Mixins(ListViewMixin) {
153
153
  }
154
154
 
155
155
  findActiveIndexForLocalSearch () {
156
+ let firstMatchingIndex = -1
157
+
156
158
  for (const [index, model] of this.models_.entries()) {
157
159
  const regex = new RegExp(`^${this.localSearchKey}`, 'i')
158
160
  if (model.getTitle().match(regex)) {
159
- if (this.activeModelIndex === index) {
161
+ if (this.activeModelIndex < 0) { // return first item found if nothing selected before
162
+ return index
163
+ }
164
+
165
+ if (this.activeModelIndex === index) { // ignore selected item
160
166
  continue
161
167
  }
162
- return index
168
+
169
+ if (firstMatchingIndex < 0) { // save first item to later jump to, if no item after selected can be found
170
+ firstMatchingIndex = index
171
+ }
172
+
173
+ if (index > this.activeModelIndex) { // return first item after selected
174
+ return index
175
+ }
163
176
  }
164
177
  }
178
+
179
+ if (firstMatchingIndex >= 0) { // jump to first item of list that matchs
180
+ return firstMatchingIndex
181
+ }
182
+
165
183
  return this.activeModelIndex
166
184
  }
167
185
  }
@@ -35,6 +35,8 @@
35
35
  <a-save-indicator />
36
36
 
37
37
  <flying-context-container />
38
+
39
+ <div id="popupContainer" />
38
40
  </div>
39
41
  </template>
40
42
 
@@ -128,4 +130,11 @@ export default class App extends Vue {
128
130
  #v-main.marginRight {
129
131
  margin-right: 60px;
130
132
  }
133
+
134
+ #popupContainer {
135
+ position: absolute;
136
+ top: 0;
137
+ left: 0;
138
+ z-index: 400;
139
+ }
131
140
  </style>
@@ -78,10 +78,14 @@ export default class FlyingContext extends Mixins(CancelOnEscMixin) {
78
78
  }
79
79
  }
80
80
 
81
- this.$el.appendChild(this.getContent())
82
- this.coe_unwatchCancel() // hide context -> do not watch esc any more
83
- this.isVisible = false
84
- this.$emit('hide')
81
+ this.$events.dispatch(new FlyingContextEvent(FlyingContextEvent.START_HIDE_CONTEXT))
82
+
83
+ setTimeout(() => { // fade in then hide contents
84
+ this.$el.appendChild(this.getContent())
85
+ this.coe_unwatchCancel() // hide context -> do not watch esc any more
86
+ this.isVisible = false
87
+ this.$emit('hide')
88
+ }, 200)
85
89
  }
86
90
  }
87
91
 
@@ -44,6 +44,8 @@ export default class FlyingContextContainer extends Vue {
44
44
 
45
45
  const sizeWatcher = new ResizeObserver(this.sizeChanged)
46
46
  sizeWatcher.observe(this.getChildrenContainer())
47
+
48
+ this.$events.on(FlyingContextEvent.START_HIDE_CONTEXT, this.onStartHideContext)
47
49
  }
48
50
 
49
51
  /**
@@ -115,18 +117,25 @@ export default class FlyingContextContainer extends Vue {
115
117
  el.style.overflowY = this.oldOverflowY
116
118
  el.style.marginRight = 0
117
119
 
118
- this.$el.style.left = '101vw' // set this if closing from outside e.g. via esc, which does not call this.hide()
120
+ this.$el.style.left = '101vw' // set this if closing from outside e.g. via context.esc, which does not call this.hide()
119
121
  }
122
+
120
123
  this.isClosing = false
121
124
  }
122
125
 
126
+ onStartHideContext () {
127
+ // context will be removed in 200ms
128
+ // we start right now and slide out the container
129
+ // so that all the contents will last until moved out of screen
130
+ this.$el.style.left = '101vw'
131
+ }
132
+
123
133
  hide () {
124
134
  if (this.visible) {
125
- this.$el.style.left = '101vw'
135
+ // ignore resize watcher while closing, prevents flickering
126
136
  this.isClosing = true
127
- setTimeout(() => { // fade in then hide contents
128
- this.$events.dispatch(new FlyingContextEvent(FlyingContextEvent.HIDE_ALL))
129
- }, 200)
137
+ // say the context that it should try to remove
138
+ this.$events.dispatch(new FlyingContextEvent(FlyingContextEvent.HIDE_ALL))
130
139
  }
131
140
  }
132
141
 
@@ -8,6 +8,7 @@ import {
8
8
  mdiCheckBold,
9
9
  mdiChevronRight,
10
10
  mdiClose,
11
+ mdiCloseCircle,
11
12
  mdiCloseThick,
12
13
  mdiCurrencyEur,
13
14
  mdiDelete,
@@ -44,6 +45,7 @@ export default new Vuetify({
44
45
  alarmIcon: mdiAlarmLightOutline,
45
46
  alertIcon: mdiAlert,
46
47
  closeIcon: mdiClose,
48
+ closeCircleIcon: mdiCloseCircle,
47
49
  closeBoldIcon: mdiCloseThick,
48
50
  dotsVerticalIcon: mdiDotsVertical,
49
51
  dotsHorizontalIcon: mdiDotsHorizontal,