@vcmap/ui 6.0.14 → 6.1.0-rc.2
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.
- package/config/base.config.json +25 -3
- package/config/dev.config.json +17 -3
- package/config/splashscreen.config.json +13 -0
- package/dist/assets/cesium.js +1 -1
- package/dist/assets/{core-882e211a.js → core-fd079400.js} +6861 -5788
- package/dist/assets/core.js +1 -1
- package/dist/assets/ol.js +1 -1
- package/dist/assets/ui-5135917c.css +1 -0
- package/dist/assets/{ui-b6bff1d9.js → ui-5135917c.js} +16764 -18447
- package/dist/assets/ui.js +1 -1
- package/dist/assets/vue.js +1 -1
- package/dist/assets/{vuetify-2d64c180.js → vuetify-f02b7bb9.js} +1 -1
- package/dist/assets/vuetify.js +1 -1
- package/index.d.ts +13 -1
- package/index.js +7 -0
- package/package.json +2 -2
- package/plugins/@vcmap-show-case/dev-tools/package.json +5 -0
- package/plugins/@vcmap-show-case/dev-tools/src/eventLogger.js +35 -0
- package/plugins/@vcmap-show-case/dev-tools/src/index.js +59 -0
- package/plugins/@vcmap-show-case/search-example/src/searchImpl.js +10 -0
- package/src/application/VcsApp.vue.d.ts +26 -0
- package/src/application/VcsContainer.vue +5 -3
- package/src/application/VcsContainer.vue.d.ts +21 -0
- package/src/application/VcsNavbar.vue +10 -6
- package/src/application/VcsNavbar.vue.d.ts +2 -0
- package/src/application/VcsSplashScreen.vue +35 -28
- package/src/application/VcsSplashScreen.vue.d.ts +1 -0
- package/src/callback/addModuleCallback.d.ts +29 -0
- package/src/callback/addModuleCallback.js +61 -0
- package/src/callback/removeModuleCallback.d.ts +29 -0
- package/src/callback/removeModuleCallback.js +53 -0
- package/src/callback/startRotationCallback.d.ts +37 -0
- package/src/callback/startRotationCallback.js +67 -0
- package/src/callback/stopRotationCallback.d.ts +8 -0
- package/src/callback/stopRotationCallback.js +37 -0
- package/src/components/buttons/VcsActionButtonList.vue +6 -4
- package/src/components/buttons/VcsToolButton.vue +0 -1
- package/src/components/form-inputs-controls/VcsDatePicker.vue +7 -1
- package/src/components/form-inputs-controls/VcsDatePicker.vue.d.ts +9 -0
- package/src/components/form-inputs-controls/VcsTextArea.vue +1 -1
- package/src/components/icons/+all.js +4 -0
- package/src/components/icons/View360Icon.vue +55 -0
- package/src/components/icons/View360Icon.vue.d.ts +2 -0
- package/src/components/import/VcsImportComponent.vue +2 -0
- package/src/components/lists/VcsList.vue +15 -11
- package/src/components/lists/VcsList.vue.d.ts +9 -0
- package/src/components/lists/VcsTreeNode.vue +244 -0
- package/src/components/lists/VcsTreeNode.vue.d.ts +31 -0
- package/src/components/lists/VcsTreeview.vue +111 -173
- package/src/components/lists/VcsTreeview.vue.d.ts +58 -4
- package/src/components/lists/VcsTreeviewTitle.vue +10 -3
- package/src/components/lists/VcsTreeviewTitle.vue.d.ts +2 -0
- package/src/components/tables/VcsDataTable.vue +14 -3
- package/src/components/tables/VcsDataTable.vue.d.ts +9 -0
- package/src/featureInfo/BalloonComponent.vue +18 -47
- package/src/featureInfo/BalloonComponent.vue.d.ts +0 -1
- package/src/featureInfo/IframeComponent.vue +1 -32
- package/src/featureInfo/IframeComponent.vue.d.ts +1 -4
- package/src/i18n/de.d.ts +1 -0
- package/src/i18n/de.js +1 -0
- package/src/i18n/en.d.ts +1 -0
- package/src/i18n/en.js +1 -0
- package/src/init.d.ts +6 -0
- package/src/init.js +26 -14
- package/src/legend/VcsLegend.vue +1 -1
- package/src/manager/toolbox/ToolboxManagerComponent.vue +4 -4
- package/src/manager/toolbox/ToolboxManagerComponent.vue.d.ts +2 -2
- package/src/manager/toolbox/toolboxManager.d.ts +5 -0
- package/src/manager/toolbox/toolboxManager.js +7 -1
- package/src/manager/window/WindowComponent.vue +11 -1
- package/src/manager/window/WindowComponent.vue.d.ts +1 -0
- package/src/manager/window/WindowManager.vue +14 -4
- package/src/manager/window/WindowManager.vue.d.ts +1 -0
- package/src/navigation/MapNavigation.vue +87 -5
- package/src/navigation/MapNavigation.vue.d.ts +3 -1
- package/src/navigation/overviewMap.d.ts +6 -0
- package/src/navigation/overviewMap.js +14 -1
- package/src/pluginHelper.d.ts +0 -7
- package/src/pluginHelper.js +4 -18
- package/src/search/ResultItem.vue +1 -10
- package/src/search/ResultsComponent.vue +11 -1
- package/src/search/ResultsComponent.vue.d.ts +9 -0
- package/src/search/SearchComponent.vue +88 -11
- package/src/search/SearchComponent.vue.d.ts +7 -0
- package/src/search/markText.d.ts +1 -1
- package/src/search/markText.js +4 -4
- package/src/search/search.d.ts +3 -0
- package/src/search/search.js +3 -2
- package/src/state.d.ts +2 -4
- package/src/state.js +31 -54
- package/src/uiConfig.d.ts +40 -0
- package/src/uiConfig.js +6 -0
- package/src/vcsUiApp.js +11 -7
- package/src/vuePlugins/vuetify.js +2 -0
- package/dist/assets/ui-b6bff1d9.css +0 -1
- /package/dist/assets/{cesium-615823f2.js → cesium-57fbd309.js} +0 -0
- /package/dist/assets/{ol-7fc05707.js → ol-50dfef96.js} +0 -0
- /package/dist/assets/{vue-74e8343e.js → vue-c3c55d88.js} +0 -0
- /package/dist/assets/{vuetify-2d64c180.css → vuetify-f02b7bb9.css} +0 -0
@@ -0,0 +1,244 @@
|
|
1
|
+
<template>
|
2
|
+
<div class="vcs-tree-node" v-if="matchFilter">
|
3
|
+
<v-row
|
4
|
+
no-gutters
|
5
|
+
class="treenode flex-nowrap text-truncate"
|
6
|
+
:class="`level-${level} ${children.length ? 'group' : 'item'}`"
|
7
|
+
>
|
8
|
+
<VBtn
|
9
|
+
v-if="children.length"
|
10
|
+
class="chevron-btn"
|
11
|
+
variant="text"
|
12
|
+
:icon="isOpen ? 'mdi-chevron-down' : 'mdi-chevron-right'"
|
13
|
+
@click="bubbleItemToggled(item.name)"
|
14
|
+
/>
|
15
|
+
<slot name="prepend" v-bind="{ item }">
|
16
|
+
<span class="prepend">
|
17
|
+
<div v-if="item.icon">
|
18
|
+
<v-icon v-if="typeof item?.icon === 'string'" :size="iconSize">
|
19
|
+
{{ item.icon }}
|
20
|
+
</v-icon>
|
21
|
+
<ImageElementInjector :element="item.icon" v-else />
|
22
|
+
</div>
|
23
|
+
</span>
|
24
|
+
</slot>
|
25
|
+
<slot name="title" v-bind="{ item }">
|
26
|
+
<VcsTreeviewTitle
|
27
|
+
:item="item"
|
28
|
+
:cursor-pointer="item.clickable || (openOnClick && !!children.length)"
|
29
|
+
@click="(event) => $emit('click', item, event)"
|
30
|
+
/>
|
31
|
+
</slot>
|
32
|
+
<slot name="append" v-bind="{ item }">
|
33
|
+
<VcsActionButtonList
|
34
|
+
v-if="item.actions?.length > 0"
|
35
|
+
:actions="item.actions"
|
36
|
+
:overflow-count="3"
|
37
|
+
:disabled="item.disabled"
|
38
|
+
right
|
39
|
+
tooltip-position="right"
|
40
|
+
block-overflow
|
41
|
+
class="col-4 pa-0 ml-auto pr-4"
|
42
|
+
/>
|
43
|
+
</slot>
|
44
|
+
</v-row>
|
45
|
+
<v-expand-transition>
|
46
|
+
<div v-if="isOpen && children.length" class="children">
|
47
|
+
<VcsTreeNode
|
48
|
+
v-for="child in children"
|
49
|
+
:key="child.name"
|
50
|
+
:item="child"
|
51
|
+
:search="search"
|
52
|
+
:custom-filter="customFilter"
|
53
|
+
:level="level + 1"
|
54
|
+
:open-on-click="openOnClick"
|
55
|
+
:item-children="itemChildren"
|
56
|
+
:opened="opened"
|
57
|
+
@item-toggled="bubbleItemToggled"
|
58
|
+
@click="bubbleItemClicked"
|
59
|
+
>
|
60
|
+
<template v-for="slot of forwardSlots" #[slot]="scope">
|
61
|
+
<slot :name="slot" v-bind="scope ?? {}" />
|
62
|
+
</template>
|
63
|
+
</VcsTreeNode>
|
64
|
+
</div>
|
65
|
+
</v-expand-transition>
|
66
|
+
</div>
|
67
|
+
</template>
|
68
|
+
|
69
|
+
<script>
|
70
|
+
import { computed, getCurrentInstance } from 'vue';
|
71
|
+
import { VBtn, VExpandTransition, VIcon, VRow } from 'vuetify/components';
|
72
|
+
import { useIconSize } from '../../vuePlugins/vuetify.js';
|
73
|
+
import { getForwardSlots } from '../composables.js';
|
74
|
+
import ImageElementInjector from '../ImageElementInjector.vue';
|
75
|
+
import VcsActionButtonList from '../buttons/VcsActionButtonList.vue';
|
76
|
+
import VcsTreeviewTitle from './VcsTreeviewTitle.vue';
|
77
|
+
|
78
|
+
/**
|
79
|
+
* @typedef {Object} VcsTreeNodeItem
|
80
|
+
* @property {string} name
|
81
|
+
* @property {string} [title] - An optional translatable title.
|
82
|
+
* @property {string} [tooltip]
|
83
|
+
* @property {boolean} [clickable] - Whether this item reacts to click events.
|
84
|
+
* @property {boolean} [disabled] - Whether this item should be displayed as disabled.
|
85
|
+
* @property {Array<import("../../actions/actionHelper.js").VcsAction>} [actions]
|
86
|
+
* @property {Array<VcsTreeNodeItem>} [children] - An optional array of children. Can be binded to another key, using the `item-children` attributes of the VcsTreeview component.
|
87
|
+
* @property {string|HTMLCanvasElement|HTMLImageElement|undefined} [icon] - An optional icon to display with this item. Can be a URL or HTMLElement.
|
88
|
+
* @property {function(string):void} [clicked] - A callback called when the item is clicked.
|
89
|
+
*/
|
90
|
+
|
91
|
+
/**
|
92
|
+
* @description
|
93
|
+
* A recursive component to render a treenode and its children.
|
94
|
+
* Exposes the `prepend`, `title` and `append` slots for customization.
|
95
|
+
* Emits `itemToggled` and `click` events.
|
96
|
+
* @vue-prop {VcsTreeNodeItem} item - The item to render.
|
97
|
+
* @vue-prop {Array<string>} opened - Names of the opened items.
|
98
|
+
* @vue-prop {number} [level=0] - The level of the item
|
99
|
+
* @vue-prop {string} [itemChildren='children'] - The property key of the children.
|
100
|
+
* @vue-prop {boolean} [openOnClick=false] - Whether to open items on title click..
|
101
|
+
* @vue-prop {string} [search] - The search string to filter the tree.
|
102
|
+
* @vue-prop {function(VcsTreeNodeItem, string|undefined):boolean}} [customFilter] - a function to customize filtering when searching.
|
103
|
+
* @vue-data {slot} [#prepend] - A slot prepended to the item, binding the item. Default fallback renders an image.
|
104
|
+
* @vue-data {slot} [#title] - A slot to render the item title, binding the item. Default fallback renders a translatable title using the VcsTreeviewTitle component.
|
105
|
+
* @vue-data {slot} [#append] - A slot appended to the item, binding the item. Default fallback renders the VcsActionButtonList if the item has an array of Actions.
|
106
|
+
*/
|
107
|
+
export default {
|
108
|
+
name: 'VcsTreeNode',
|
109
|
+
components: {
|
110
|
+
ImageElementInjector,
|
111
|
+
VBtn,
|
112
|
+
VExpandTransition,
|
113
|
+
VIcon,
|
114
|
+
VRow,
|
115
|
+
VcsActionButtonList,
|
116
|
+
VcsTreeviewTitle,
|
117
|
+
},
|
118
|
+
props: {
|
119
|
+
/** @type {VcsTreeNodeItem} */
|
120
|
+
item: {
|
121
|
+
type: Object,
|
122
|
+
required: true,
|
123
|
+
},
|
124
|
+
opened: {
|
125
|
+
type: Array,
|
126
|
+
required: true,
|
127
|
+
},
|
128
|
+
level: {
|
129
|
+
type: Number,
|
130
|
+
default: 0,
|
131
|
+
},
|
132
|
+
openOnClick: {
|
133
|
+
type: Boolean,
|
134
|
+
default: false,
|
135
|
+
},
|
136
|
+
itemChildren: {
|
137
|
+
type: String,
|
138
|
+
default: 'children',
|
139
|
+
},
|
140
|
+
search: {
|
141
|
+
type: String,
|
142
|
+
default: undefined,
|
143
|
+
},
|
144
|
+
/**
|
145
|
+
* @param {VcsTreeNodeItem} item The item to check the match for.
|
146
|
+
* @param {string} search The search value.
|
147
|
+
*/
|
148
|
+
customFilter: {
|
149
|
+
type: Function,
|
150
|
+
default: undefined,
|
151
|
+
},
|
152
|
+
},
|
153
|
+
emits: ['itemToggled', 'click'],
|
154
|
+
setup(props, { emit, slots }) {
|
155
|
+
const vm = getCurrentInstance().proxy;
|
156
|
+
const iconSize = useIconSize();
|
157
|
+
const forwardSlots = getForwardSlots(slots);
|
158
|
+
|
159
|
+
const isOpen = computed(() => props.opened.includes(props.item.name));
|
160
|
+
const children = computed(() => props.item[props.itemChildren] ?? []);
|
161
|
+
|
162
|
+
const matchFilter = computed(() => {
|
163
|
+
if (!props.search) {
|
164
|
+
return true;
|
165
|
+
}
|
166
|
+
if (props.customFilter) {
|
167
|
+
return props.customFilter(props.item, props.search);
|
168
|
+
}
|
169
|
+
const translatedTitle = (item) =>
|
170
|
+
item.title ? vm.$t(item.title) : item.name;
|
171
|
+
|
172
|
+
const hasText = (item) =>
|
173
|
+
translatedTitle(item)
|
174
|
+
.toLocaleLowerCase()
|
175
|
+
.indexOf(props.search.toLocaleLowerCase()) > -1 ||
|
176
|
+
item[props.itemChildren]?.some(hasText);
|
177
|
+
|
178
|
+
return hasText(props.item);
|
179
|
+
});
|
180
|
+
|
181
|
+
return {
|
182
|
+
forwardSlots,
|
183
|
+
isOpen,
|
184
|
+
matchFilter,
|
185
|
+
iconSize,
|
186
|
+
children,
|
187
|
+
|
188
|
+
// Bubble up events for the nested tree-items
|
189
|
+
bubbleItemToggled: (itemName) => {
|
190
|
+
emit('itemToggled', itemName);
|
191
|
+
},
|
192
|
+
bubbleItemClicked: (item, event) => {
|
193
|
+
emit('click', item, event);
|
194
|
+
},
|
195
|
+
};
|
196
|
+
},
|
197
|
+
};
|
198
|
+
</script>
|
199
|
+
|
200
|
+
<style lang="scss" scoped>
|
201
|
+
.treenode {
|
202
|
+
align-items: center !important;
|
203
|
+
min-height: calc(var(--v-vcs-font-size) * 2 + 6px);
|
204
|
+
}
|
205
|
+
@for $i from 0 through 6 {
|
206
|
+
.level-#{$i} {
|
207
|
+
padding-left: calc(var(--v-vcs-font-size) * $i + 2px);
|
208
|
+
&.item {
|
209
|
+
& > .prepend:not(:has(.v-icon)) {
|
210
|
+
margin-left: calc(var(--v-vcs-font-size) * 2);
|
211
|
+
}
|
212
|
+
& > .prepend:has(.v-icon) {
|
213
|
+
margin-left: 4px;
|
214
|
+
}
|
215
|
+
}
|
216
|
+
}
|
217
|
+
}
|
218
|
+
.prepend {
|
219
|
+
:deep(.v-icon) {
|
220
|
+
margin-right: 6px;
|
221
|
+
display: flex;
|
222
|
+
align-items: start;
|
223
|
+
}
|
224
|
+
}
|
225
|
+
// Indent children padding
|
226
|
+
.children {
|
227
|
+
padding-left: var(--v-vcs-font-size);
|
228
|
+
}
|
229
|
+
// set the size of the buttons, except for the ActionButtonList
|
230
|
+
:deep(.v-btn):not(.vcs-button) {
|
231
|
+
width: var(--v-vcs-font-size);
|
232
|
+
height: var(--v-vcs-font-size);
|
233
|
+
// for alignment of chevron
|
234
|
+
display: flex;
|
235
|
+
&.chevron-btn {
|
236
|
+
margin-left: 4px;
|
237
|
+
margin-right: 8px;
|
238
|
+
}
|
239
|
+
}
|
240
|
+
// remove hover shadow over button
|
241
|
+
:deep(.v-btn__overlay) {
|
242
|
+
--v-hover-opacity: 0;
|
243
|
+
}
|
244
|
+
</style>
|
@@ -0,0 +1,31 @@
|
|
1
|
+
declare const _default: import("vue").DefineSetupFnComponent<Record<string, any>, {}, {}, Record<string, any> & {}, import("vue").PublicProps>;
|
2
|
+
export default _default;
|
3
|
+
export type VcsTreeNodeItem = {
|
4
|
+
name: string;
|
5
|
+
/**
|
6
|
+
* - An optional translatable title.
|
7
|
+
*/
|
8
|
+
title?: string | undefined;
|
9
|
+
tooltip?: string | undefined;
|
10
|
+
/**
|
11
|
+
* - Whether this item reacts to click events.
|
12
|
+
*/
|
13
|
+
clickable?: boolean | undefined;
|
14
|
+
/**
|
15
|
+
* - Whether this item should be displayed as disabled.
|
16
|
+
*/
|
17
|
+
disabled?: boolean | undefined;
|
18
|
+
actions?: import("../../actions/actionHelper.js", { with: { "resolution-mode": "import" } }).VcsAction[] | undefined;
|
19
|
+
/**
|
20
|
+
* - An optional array of children. Can be binded to another key, using the `item-children` attributes of the VcsTreeview component.
|
21
|
+
*/
|
22
|
+
children?: VcsTreeNodeItem[] | undefined;
|
23
|
+
/**
|
24
|
+
* - An optional icon to display with this item. Can be a URL or HTMLElement.
|
25
|
+
*/
|
26
|
+
icon?: string | HTMLCanvasElement | HTMLImageElement | undefined;
|
27
|
+
/**
|
28
|
+
* - A callback called when the item is clicked.
|
29
|
+
*/
|
30
|
+
clicked?: ((arg0: string) => void) | undefined;
|
31
|
+
};
|
@@ -1,163 +1,82 @@
|
|
1
1
|
<template>
|
2
|
-
<div class="
|
2
|
+
<div class="vcs-treeview">
|
3
3
|
<VcsTreeviewSearchbar
|
4
4
|
v-if="showSearchbar"
|
5
5
|
:placeholder="searchbarPlaceholder"
|
6
6
|
v-model="localSearchValue"
|
7
7
|
/>
|
8
|
-
<v-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
</slot>
|
26
|
-
</template>
|
27
|
-
<template v-for="slot of forwardSlots" #[slot]="scope">
|
28
|
-
<slot :name="slot" v-bind="scope ?? {}" />
|
29
|
-
</template>
|
30
|
-
<template #prepend="scope">
|
31
|
-
<slot name="prepend" v-bind="scope ?? {}">
|
32
|
-
<template v-if="scope.item?.icon">
|
33
|
-
<v-icon
|
34
|
-
v-if="typeof scope.item?.icon === 'string'"
|
35
|
-
:size="iconSize"
|
36
|
-
>
|
37
|
-
{{ scope.item.icon }}
|
38
|
-
</v-icon>
|
39
|
-
<ImageElementInjector :element="scope.item.icon" v-else />
|
40
|
-
</template>
|
41
|
-
</slot>
|
42
|
-
</template>
|
43
|
-
<template #append="scope">
|
44
|
-
<slot name="append" v-bind="scope ?? {}">
|
45
|
-
<VcsActionButtonList
|
46
|
-
v-if="scope.item.actions?.length > 0"
|
47
|
-
:actions="scope.item.actions"
|
48
|
-
:overflow-count="3"
|
49
|
-
:disabled="scope.item.disabled"
|
50
|
-
right
|
51
|
-
tooltip-position="right"
|
52
|
-
block-overflow
|
53
|
-
class="col-4 pa-0 d-flex align-center"
|
54
|
-
/>
|
55
|
-
</slot>
|
56
|
-
</template>
|
57
|
-
</v-treeview>
|
8
|
+
<div v-for="item in items" :key="item.name" class="vcs-treeitem">
|
9
|
+
<VcsTreeNode
|
10
|
+
class="root-node"
|
11
|
+
:item="item"
|
12
|
+
:search="localSearchValue"
|
13
|
+
v-model:opened="localOpenedItems"
|
14
|
+
:custom-filter="customFilter"
|
15
|
+
:open-on-click="openOnClick"
|
16
|
+
:item-children="itemChildren"
|
17
|
+
@item-toggled="itemToggled"
|
18
|
+
@click="itemClicked"
|
19
|
+
>
|
20
|
+
<template v-for="slot of forwardSlots" #[slot]="scope">
|
21
|
+
<slot :name="slot" v-bind="scope ?? {}" />
|
22
|
+
</template>
|
23
|
+
</VcsTreeNode>
|
24
|
+
</div>
|
58
25
|
</div>
|
59
26
|
</template>
|
60
|
-
<style lang="scss" scoped>
|
61
|
-
:deep(.vcs-treeview) {
|
62
|
-
// Root Level Entries should be 40px high
|
63
|
-
> .v-list-item,
|
64
|
-
> .v-list-group > .v-list-item {
|
65
|
-
min-height: calc(var(--v-vcs-font-size) * 2 + 14px) !important;
|
66
|
-
padding-left: 6px;
|
67
|
-
}
|
68
|
-
// Border around root nodes with children included
|
69
|
-
> .v-list-item:not(:last-child),
|
70
|
-
> .v-list-group:not(:last-child) {
|
71
|
-
border-bottom: 1px solid rgb(var(--v-theme-base-lighten-2));
|
72
|
-
}
|
73
|
-
// Only Group Entries have a bold font
|
74
|
-
> .v-list-group
|
75
|
-
> .v-list-item
|
76
|
-
> .v-list-item__content
|
77
|
-
> .v-list-item-title {
|
78
|
-
font-weight: 700 !important;
|
79
|
-
}
|
80
|
-
}
|
81
|
-
|
82
|
-
// leaf indent
|
83
|
-
:deep(.v-list--slim .v-treeview-group.v-list-group) {
|
84
|
-
--prepend-width: 0px;
|
85
|
-
}
|
86
|
-
|
87
|
-
// Padding left of root nodes
|
88
|
-
:deep(.v-list-item__prepend) {
|
89
|
-
width: var(--v-vcs-font-size);
|
90
|
-
margin-right: 8px;
|
91
|
-
> .v-list-item-action > .v-btn {
|
92
|
-
width: var(--v-vcs-font-size);
|
93
|
-
height: var(--v-vcs-font-size);
|
94
|
-
margin: auto;
|
95
|
-
// for alignment of chevron
|
96
|
-
display: flex;
|
97
|
-
}
|
98
|
-
}
|
99
|
-
|
100
|
-
// Width of prepend for group nodes having two icons (chevron and custom icon)
|
101
|
-
:deep(.v-list-item__prepend:has(> .v-list-item-action + .v-icon)) {
|
102
|
-
width: calc(var(--v-vcs-font-size) + 23px);
|
103
|
-
.v-list-item-action {
|
104
|
-
margin-right: calc(22px - var(--v-vcs-font-size));
|
105
|
-
}
|
106
|
-
}
|
107
|
-
|
108
|
-
// remove hover shadow over button
|
109
|
-
:deep(.v-btn__overlay) {
|
110
|
-
--v-hover-opacity: 0;
|
111
|
-
}
|
112
|
-
// remove ripple effect
|
113
|
-
:deep(.v-ripple__container) {
|
114
|
-
display: none;
|
115
|
-
}
|
116
|
-
// hide active class
|
117
|
-
:deep(.v-list-item__overlay) {
|
118
|
-
display: none;
|
119
|
-
}
|
120
|
-
|
121
|
-
.d-contents {
|
122
|
-
display: contents;
|
123
|
-
}
|
124
|
-
</style>
|
125
27
|
|
126
28
|
<script>
|
127
|
-
import {
|
128
|
-
import {
|
129
|
-
|
130
|
-
|
29
|
+
import { watch } from 'vue';
|
30
|
+
import {
|
31
|
+
useProxiedAtomicModel,
|
32
|
+
useProxiedComplexModel,
|
33
|
+
} from '../modelHelper.js';
|
131
34
|
import { getForwardSlots } from '../composables.js';
|
132
35
|
import VcsTreeviewSearchbar from './VcsTreeviewSearchbar.vue';
|
133
|
-
import
|
134
|
-
import ImageElementInjector from '../ImageElementInjector.vue';
|
135
|
-
import VcsTreeviewTitle from './VcsTreeviewTitle.vue';
|
136
|
-
import { useIconSize } from '../../vuePlugins/vuetify.js';
|
36
|
+
import VcsTreeNode from './VcsTreeNode.vue';
|
137
37
|
|
138
38
|
/**
|
139
|
-
* @description
|
140
|
-
* Can render dynamic components as leaf items.
|
141
|
-
* In order to display an item needs to be registered and added to `availableComponents`.
|
39
|
+
* @description The VcsTreeview is heavily inspired by the Vuetify VTreeview component. Can render dynamic components as leaf items.
|
142
40
|
* Exposes the `search` value for filtering the treeview.
|
143
|
-
*
|
144
|
-
*
|
41
|
+
* Exposes the `opened` model-value for controlling the opened state of the treeview.
|
42
|
+
* Forwards the `prepend`, `title` and `append` slots to the VcsTreeNode component.
|
43
|
+
* @vue-prop {Array<import("./VcsTreeNode.vue").VcsTreeNodeItem>} items.
|
44
|
+
* @vue-prop {Array<import("./VcsTreeNode.vue").VcsTreeNodeItem>} opened - Array of name of opened nodes.
|
45
|
+
* @vue-prop {string} [itemChildren='children'] - The property key of the children.
|
46
|
+
* @vue-prop {boolean} [openAll=false] - Whether to open all root items on startup.
|
47
|
+
* @vue-prop {boolean} [openOnClick=false] - Whether to open items on title click.
|
48
|
+
* @vue-prop {string} [search=''] - The value used to filter the items.
|
49
|
+
* @vue-prop {boolean} [showSearchbar=false] - Whether there is a searchbar for this treeview.
|
50
|
+
* @vue-prop {function(import("./VcsTreeNode.vue").VcsTreeNodeItem, string|undefined):boolean}} [customFilter] - a function to customize filtering when searching.
|
51
|
+
* @vue-prop {string} [searchbarPlaceholder] - Placeholder text for the searchbar, will be translated.
|
145
52
|
*/
|
146
53
|
export default {
|
147
54
|
name: 'VcsTreeview',
|
148
55
|
components: {
|
149
|
-
VcsTreeviewTitle,
|
150
|
-
VIcon,
|
151
|
-
ImageElementInjector,
|
152
|
-
VcsActionButtonList,
|
153
56
|
VcsTreeviewSearchbar,
|
154
|
-
|
57
|
+
VcsTreeNode,
|
155
58
|
},
|
156
59
|
props: {
|
157
60
|
items: {
|
158
61
|
type: Array,
|
159
62
|
default: () => [],
|
160
63
|
},
|
64
|
+
opened: {
|
65
|
+
type: Array,
|
66
|
+
default: () => [],
|
67
|
+
},
|
68
|
+
itemChildren: {
|
69
|
+
type: String,
|
70
|
+
default: 'children',
|
71
|
+
},
|
72
|
+
openAll: {
|
73
|
+
type: Boolean,
|
74
|
+
default: false,
|
75
|
+
},
|
76
|
+
openOnClick: {
|
77
|
+
type: Boolean,
|
78
|
+
default: false,
|
79
|
+
},
|
161
80
|
search: {
|
162
81
|
type: String,
|
163
82
|
default: '',
|
@@ -170,59 +89,78 @@
|
|
170
89
|
type: String,
|
171
90
|
default: undefined,
|
172
91
|
},
|
92
|
+
/**
|
93
|
+
* @param {import("./VcsTreeNode.vue").VcsTreeNodeItem} item The item to filter.
|
94
|
+
* @param {string} search The query value.
|
95
|
+
*/
|
96
|
+
customFilter: {
|
97
|
+
type: Function,
|
98
|
+
default: undefined,
|
99
|
+
},
|
173
100
|
},
|
174
|
-
emits: ['update:search'],
|
101
|
+
emits: ['update:search', 'update:opened'],
|
175
102
|
setup(props, { emit, slots }) {
|
103
|
+
const forwardSlots = getForwardSlots(slots);
|
176
104
|
const localSearchValue = useProxiedAtomicModel(props, 'search', emit);
|
105
|
+
const localOpenedItems = useProxiedComplexModel(props, 'opened', emit);
|
177
106
|
|
178
|
-
|
107
|
+
if ((props.openAll ?? false) !== false) {
|
108
|
+
localOpenedItems.value = props.items.map((item) => item.name);
|
109
|
+
watch(
|
110
|
+
() => props.items,
|
111
|
+
(items) => {
|
112
|
+
const newItems = items.filter(
|
113
|
+
(item) => !localOpenedItems.value.includes(item.name),
|
114
|
+
);
|
115
|
+
localOpenedItems.value.push(...newItems.map((item) => item.name));
|
116
|
+
},
|
117
|
+
);
|
118
|
+
}
|
179
119
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
*/
|
187
|
-
const handleFilter = (value, q, item) => {
|
188
|
-
if (value == null || q == null) {
|
189
|
-
return -1;
|
120
|
+
function itemToggled(itemName) {
|
121
|
+
const idx = localOpenedItems.value.indexOf(itemName);
|
122
|
+
if (idx >= 0) {
|
123
|
+
localOpenedItems.value.splice(idx, 1);
|
124
|
+
} else {
|
125
|
+
localOpenedItems.value.push(itemName);
|
190
126
|
}
|
191
|
-
|
192
|
-
return translatedTitle
|
193
|
-
.toLocaleLowerCase()
|
194
|
-
.indexOf(q.toLocaleLowerCase());
|
195
|
-
};
|
127
|
+
}
|
196
128
|
|
197
|
-
const forwardSlots = getForwardSlots(slots, [
|
198
|
-
'append',
|
199
|
-
'title',
|
200
|
-
'prepend',
|
201
|
-
]);
|
202
|
-
const iconSize = useIconSize();
|
203
129
|
return {
|
204
|
-
iconSize,
|
205
130
|
localSearchValue,
|
206
|
-
|
131
|
+
localOpenedItems,
|
207
132
|
forwardSlots,
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
if (item.name === name) {
|
214
|
-
break;
|
215
|
-
}
|
216
|
-
if (item.children?.length > 0) {
|
217
|
-
items.push(...item.children);
|
133
|
+
itemToggled,
|
134
|
+
itemClicked(item, event) {
|
135
|
+
if (item?.clickable) {
|
136
|
+
if (item?.clicked && !item?.disabled) {
|
137
|
+
item.clicked(event);
|
218
138
|
}
|
219
|
-
}
|
220
|
-
|
221
|
-
if (item?.clicked && !item?.disabled) {
|
222
|
-
item.clicked(event);
|
139
|
+
} else if ((props.openOnClick ?? false) !== false) {
|
140
|
+
itemToggled(item.name);
|
223
141
|
}
|
224
142
|
},
|
225
143
|
};
|
226
144
|
},
|
227
145
|
};
|
228
146
|
</script>
|
147
|
+
|
148
|
+
<style lang="scss" scoped>
|
149
|
+
// Hide node component when not rendered (e.g. filtered by search)
|
150
|
+
.vcs-treeitem:not(:has(.vcs-tree-node)) {
|
151
|
+
display: none;
|
152
|
+
}
|
153
|
+
.vcs-treeitem:not(:last-child) {
|
154
|
+
border-bottom: 1px solid rgb(var(--v-theme-base-lighten-2));
|
155
|
+
}
|
156
|
+
.root-node {
|
157
|
+
:deep(.level-0) {
|
158
|
+
// Root Level Entries should be 40px high
|
159
|
+
min-height: calc(var(--v-vcs-font-size) * 2 + 14px) !important;
|
160
|
+
}
|
161
|
+
// Only Group Entries have a bold font
|
162
|
+
> :deep(.group) {
|
163
|
+
font-weight: 700 !important;
|
164
|
+
}
|
165
|
+
}
|
166
|
+
</style>
|