@afeefa/vue-app 0.0.194 → 0.0.196

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,