@webitel/ui-sdk 24.12.60 → 24.12.62

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webitel/ui-sdk",
3
- "version": "24.12.60",
3
+ "version": "24.12.62",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "dev": "vite",
@@ -179,10 +179,11 @@ import sttSearch from './stt-search.svg';
179
179
  import telegramBot from './telegram-bot.svg';
180
180
  import tick from './tick.svg';
181
181
  import treeCollapse from './tree-collapse.svg';
182
- import treeCross from './tree-cross.svg';
183
182
  import treeExpand from './tree-expand.svg';
184
- import treeLine from './tree-line.svg';
185
183
  import ttsDownload from './tts-download.svg';
184
+ import treeLine from './tree-line.svg';
185
+ import treeCorner from './tree-corner.svg';
186
+ import treeCross from './tree-cross.svg';
186
187
  import undo from './undo.svg';
187
188
  import unpin from './unpin.svg';
188
189
  import upload from './upload.svg';
@@ -354,6 +355,7 @@ export default objCamelToKebab({
354
355
  zoomOut,
355
356
  ttsDownload,
356
357
  treeLine,
358
+ treeCorner,
357
359
  treeCross,
358
360
  stt,
359
361
  sttDownload,
@@ -0,0 +1,3 @@
1
+ <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M12 0V13H24V12L13 12L13 0H12Z"/>
3
+ </svg>
@@ -1,11 +1,14 @@
1
+ import WtNavigationMenu from './on-demand/wt-navigation-menu/components/wt-navigation-menu.vue';
2
+ import WtSelectionPopup from './on-demand/wt-selection-popup/wt-selection-popup.vue';
3
+ import WtStartPage from './on-demand/wt-start-page/components/wt-start-page.vue';
1
4
  import WtActionBar from './wt-action-bar/wt-action-bar.vue';
2
5
  import WtAppHeader from './wt-app-header/wt-app-header.vue';
3
6
  import WtAppNavigator from './wt-app-header/wt-app-navigator.vue';
4
7
  import WtHeaderActions from './wt-app-header/wt-header-actions.vue';
5
8
  import WtAvatar from './wt-avatar/wt-avatar.vue';
6
9
  import WtBadge from './wt-badge/wt-badge.vue';
7
- import WtButtonSelect from './wt-button-select/wt-button-select.vue';
8
10
  import WtButton from './wt-button/wt-button.vue';
11
+ import WtButtonSelect from './wt-button-select/wt-button-select.vue';
9
12
  import WtCheckbox from './wt-checkbox/wt-checkbox.vue';
10
13
  import WtChip from './wt-chip/wt-chip.vue';
11
14
  import WtConfirmDialog from './wt-confirm-dialog/wt-confirm-dialog.vue';
@@ -13,21 +16,22 @@ import WtContextMenu from './wt-context-menu/wt-context-menu.vue';
13
16
  import WtCopyAction from './wt-copy-action/wt-copy-action.vue';
14
17
  import WtDatepicker from './wt-datepicker/wt-datepicker.vue';
15
18
  import WtDivider from './wt-divider/wt-divider.vue';
19
+ import WtDualPanel from './wt-dual-panel/wt-dual-panel.vue';
16
20
  import WtDummy from './wt-dummy/wt-dummy.vue';
17
21
  import WtEmpty from './wt-empty/wt-empty.vue';
18
22
  import WtErrorPage from './wt-error-page/wt-error-page.vue';
19
23
  import WtExpansionPanel from './wt-expansion-panel/wt-expansion-panel.vue';
20
24
  import WtFiltersPanelWrapper from './wt-filters-panel-wrapper/wt-filters-panel-wrapper.vue';
21
- import WtHeadlineNav from './wt-headline-nav/wt-headline-nav.vue';
22
25
  import WtHeadline from './wt-headline/wt-headline.vue';
26
+ import WtHeadlineNav from './wt-headline-nav/wt-headline-nav.vue';
23
27
  import WtHint from './wt-hint/wt-hint.vue';
28
+ import WtIcon from './wt-icon/wt-icon.vue';
24
29
  import WtIconAction from './wt-icon-action/wt-icon-action.vue';
25
30
  import WtIconBtn from './wt-icon-btn/wt-icon-btn.vue';
26
- import WtIcon from './wt-icon/wt-icon.vue';
27
31
  import WtImage from './wt-image/wt-image.vue';
28
32
  import WtIndicator from './wt-indicator/wt-indicator.vue';
29
- import WtInputInfo from './wt-input-info/wt-input-info.vue';
30
33
  import WtInput from './wt-input/wt-input.vue';
34
+ import WtInputInfo from './wt-input-info/wt-input-info.vue';
31
35
  import WtIntersectionObserver from './wt-intersection-observer/wt-intersection-observer.vue';
32
36
  import WtItemLink from './wt-item-link/wt-item-link.vue';
33
37
  import WtLabel from './wt-label/wt-label.vue';
@@ -39,7 +43,6 @@ import WtNotification from './wt-notification/wt-notification.vue';
39
43
  import WtNotificationsBar from './wt-notifications-bar/wt-notifications-bar.vue';
40
44
  import WtPageHeader from './wt-page-header/wt-page-header.vue';
41
45
  import WtPageWrapper from './wt-page-wrapper/wt-page-wrapper.vue';
42
- import WtDualPanel from './wt-dual-panel/wt-dual-panel.vue';
43
46
  import WtPagination from './wt-pagination/wt-pagination.vue';
44
47
  import WtPlayer from './wt-player/wt-player.vue';
45
48
  import WtPopup from './wt-popup/wt-popup.vue';
@@ -52,19 +55,17 @@ import WtSlider from './wt-slider/wt-slider.vue';
52
55
  import WtStatusSelect from './wt-status-select/wt-status-select.vue';
53
56
  import WtStepper from './wt-stepper/wt-stepper.vue';
54
57
  import WtSwitcher from './wt-switcher/wt-switcher.vue';
58
+ import WtTable from './wt-table/wt-table.vue';
55
59
  import WtTableActions from './wt-table-actions/wt-table-actions.vue';
56
60
  import WtTableColumnSelect from './wt-table-column-select/wt-table-column-select.vue';
57
- import WtTable from './wt-table/wt-table.vue';
58
- import WtTreeTable from './wt-tree-table/wt-tree-table.vue';
59
61
  import WtTabs from './wt-tabs/wt-tabs.vue';
60
62
  import WtTagsInput from './wt-tags-input/wt-tags-input.vue';
61
63
  import WtTextarea from './wt-textarea/wt-textarea.vue';
62
64
  import WtTimeInput from './wt-time-input/wt-time-input.vue';
63
65
  import WtTimepicker from './wt-timepicker/wt-timepicker.vue';
64
66
  import WtTooltip from './wt-tooltip/wt-tooltip.vue';
65
- import WtNavigationMenu from './on-demand/wt-navigation-menu/components/wt-navigation-menu.vue';
66
- import WtStartPage from './on-demand/wt-start-page/components/wt-start-page.vue';
67
- import WtSelectionPopup from './on-demand/wt-selection-popup/wt-selection-popup.vue';
67
+ import WtTree from './wt-tree/wt-tree.vue';
68
+ import WtTreeTable from './wt-tree-table/wt-tree-table.vue';
68
69
 
69
70
  const Components = {
70
71
  WtActionBar,
@@ -118,6 +119,7 @@ const Components = {
118
119
  WtPlayer,
119
120
  WtStatusSelect,
120
121
  WtTable,
122
+ WtTree,
121
123
  WtTreeTable,
122
124
  WtTableActions,
123
125
  WtTableColumnSelect,
@@ -1,5 +1,6 @@
1
1
  import VueMultiselect from 'vue-multiselect';
2
2
  import { ObserveVisibility } from 'vue-observe-visibility';
3
+
3
4
  import validationMixin from '../../../mixins/validationMixin/validationMixin.js';
4
5
  import debounce from '../../../scripts/debounce.js';
5
6
  import isEmpty from '../../../scripts/isEmpty.js';
@@ -82,16 +83,34 @@ export default {
82
83
  showIntersectionObserver() {
83
84
  return this.isApiMode && !this.isLoading && this.apiOptions.length;
84
85
  },
85
- // vue-multiselect doesn't show placeholder if value is empty object
86
86
  selectValue() {
87
+ /* vue-multiselect doesn't show placeholder if value is empty object */
87
88
  if (!this.isValue) return '';
88
89
 
90
+ let returnedValue = this.value;
91
+
92
+ /*
93
+ coerce single value to array, if it was passed with `multiple` prop
94
+ by dev mistake or internal error
95
+ */
96
+ if (this.multiple && !Array.isArray(returnedValue)) {
97
+ console.warn(
98
+ 'wt-select: value prop should be an array when using multiple mode',
99
+ );
100
+ returnedValue = [returnedValue];
101
+ }
102
+
89
103
  if (this.useValueFromOptionsByProp) {
90
- const valueFromOptions = this.cachedOptionsMap[this.value];
91
- if (valueFromOptions) return valueFromOptions;
104
+ if (this.multiple) {
105
+ returnedValue = returnedValue.map(
106
+ (item) => this.cachedOptionsMap[item] || item,
107
+ );
108
+ } else {
109
+ returnedValue = this.cachedOptionsMap[returnedValue] || returnedValue;
110
+ }
92
111
  }
93
112
 
94
- return this.value;
113
+ return returnedValue;
95
114
  },
96
115
 
97
116
  selectOptions() {
@@ -155,9 +174,16 @@ export default {
155
174
  },
156
175
 
157
176
  input(value) {
158
- const emittedValue = this.useValueFromOptionsByProp
159
- ? value[this.useValueFromOptionsByProp]
160
- : value;
177
+ let emittedValue = value;
178
+ if (this.useValueFromOptionsByProp) {
179
+ if (this.multiple) {
180
+ emittedValue = value.map(
181
+ (item) => item[this.useValueFromOptionsByProp],
182
+ );
183
+ } else {
184
+ emittedValue = value[this.useValueFromOptionsByProp];
185
+ }
186
+ }
161
187
  this.$emit('input', emittedValue); // vue 2
162
188
  this.$emit('update:model-value', emittedValue); // vue 3
163
189
  },
@@ -174,12 +200,46 @@ export default {
174
200
  // load options if becomes enabled
175
201
  if (!this.disabled) this.fetchOptions();
176
202
  },
177
- options: {
203
+ selectOptions: {
178
204
  handler() {
179
205
  if (this.trackBy === null) return; // then, options are primitives
180
206
 
181
- for (const opt of this.options) {
182
- this.cachedOptionsMap[opt[this.useValueFromOptionsByProp || this.trackBy]] = opt;
207
+ for (const opt of this.selectOptions) {
208
+ this.cachedOptionsMap[
209
+ opt[this.useValueFromOptionsByProp || this.trackBy]
210
+ ] = opt;
211
+ }
212
+ },
213
+ immediate: true,
214
+ },
215
+ value: {
216
+ async handler() {
217
+ /*
218
+ use case: select api option while using `useValueFromOptionsByProp` prop,
219
+ then, refresh page and restore selected id. but it may not be in options list,
220
+
221
+ when using api-fetched options, selected value, tracked by id,
222
+ may be not in returned options list,
223
+ so its necessary to fetch those values separately
224
+ */
225
+ if (this.useValueFromOptionsByProp && this.isApiMode) {
226
+ const valuesArr = Array.isArray(this.value)
227
+ ? this.value
228
+ : [this.value];
229
+ const uncachedValues = valuesArr.filter(() => {
230
+ return !this.cachedOptionsMap[this.value];
231
+ });
232
+
233
+ if (uncachedValues.length) {
234
+ const { items } = await this.searchMethod({
235
+ id: uncachedValues,
236
+ size: uncachedValues.length,
237
+ });
238
+ items.forEach((item) => {
239
+ this.cachedOptionsMap[item[this.useValueFromOptionsByProp]] =
240
+ item;
241
+ });
242
+ }
183
243
  }
184
244
  },
185
245
  immediate: true,
@@ -27,6 +27,10 @@
27
27
  <vue-multiselect
28
28
  ref="vue-multiselect"
29
29
  :allow-empty="allowEmpty"
30
+ :close-on-select="
31
+ $attrs.closeOnSelect ||
32
+ !multiple /* override default vue-multiselect value */
33
+ "
30
34
  :disabled="disabled"
31
35
  :internal-search="!searchMethod"
32
36
  :label="selectOptionLabel"
@@ -0,0 +1,13 @@
1
+ import { mount, shallowMount } from '@vue/test-utils';
2
+ import WtTree from '../wt-tree.vue';
3
+
4
+ describe('WtTree', () => {
5
+ it('renders a component', () => {
6
+ const wrapper = shallowMount(WtTree, {
7
+ stubs: {
8
+ WtTree: true,
9
+ },
10
+ });
11
+ expect(wrapper.classes('wt-tree')).toBe(true);
12
+ });
13
+ });
@@ -0,0 +1,4 @@
1
+ export enum WtTreeMode {
2
+ TREE = 'tree',
3
+ LIST = 'list',
4
+ }
@@ -0,0 +1,178 @@
1
+ <template>
2
+ <div class="wt-tree">
3
+ <div
4
+ v-if="mode === WtTreeMode.TREE"
5
+ class="wt-tree__content"
6
+ >
7
+ <wt-tree-line
8
+ v-for="(item, index) in data"
9
+ :key="index"
10
+ :model-value="modelValue"
11
+ :item-label="itemLabel"
12
+ :item-data="itemData"
13
+ :data="item"
14
+ :children-prop="childrenProp"
15
+ @update:model-value="emit('update:modelValue', $event)"
16
+ />
17
+ </div>
18
+ <div
19
+ v-if="mode === WtTreeMode.LIST"
20
+ class="wt-tree__list-content"
21
+ >
22
+ <span
23
+ v-for="(item, index) in allData"
24
+ :key="index"
25
+ class="wt-tree__label-wrapper"
26
+ :class="{ active: compareSelectElement(item) }"
27
+ >
28
+ <p
29
+ class="wt-tree__label"
30
+ @click="selectElement(item)"
31
+ >
32
+ {{ itemLabel ? item[itemLabel] : item }}
33
+ </p>
34
+ <wt-icon
35
+ v-if="compareSelectElement(item)"
36
+ icon="chat-message-status-sent"
37
+ class="wt-tree__label-icon"
38
+ />
39
+ </span>
40
+ </div>
41
+ </div>
42
+ </template>
43
+
44
+ <script setup lang="ts">
45
+ import deepEqual from 'deep-equal';
46
+ import { computed } from 'vue';
47
+
48
+ import WtTreeLine from '../wt-tree-line/wt-tree-line.vue';
49
+ import { WtTreeMode } from './types/wt-tree-mode.ts';
50
+
51
+ const props = withDefaults(
52
+ defineProps<{
53
+ /**
54
+ * Selected element, it can be an object or a string, related to itemData props
55
+ */
56
+ modelValue?: null | any;
57
+ /**
58
+ * You need to pass an array of objects that will be displayed in the tree
59
+ */
60
+ data: any[];
61
+ /**
62
+ * You can pass the name of the property that will be used as the label of the selected item
63
+ */
64
+ itemLabel?: string;
65
+ /**
66
+ * You can pass the name of the property that will be used as the value of the selected item
67
+ */
68
+ itemData?: string;
69
+ /**
70
+ * Select mode for display tree data
71
+ * @example 'tree', 'list'
72
+ */
73
+ mode?: string;
74
+ /**
75
+ * You can pass the name of the property that will be used for getting children elements
76
+ */
77
+ childrenProp?: string;
78
+ }>(),
79
+ {
80
+ childrenProp: 'children',
81
+ mode: WtTreeMode.TREE,
82
+ },
83
+ );
84
+
85
+ const emit = defineEmits<{
86
+ (e: 'select', data: any): void;
87
+ (e: 'update:modelValue', value: any): void;
88
+ }>();
89
+
90
+ const allData = computed(() => {
91
+ const result = [];
92
+
93
+ const getNestedItems = (item: any) => {
94
+ result.push(item);
95
+
96
+ if (item[props.childrenProp]) {
97
+ item[props.childrenProp].forEach((child: any) => {
98
+ getNestedItems(child);
99
+ });
100
+ }
101
+ };
102
+
103
+ props.data.forEach((item) => {
104
+ getNestedItems(item);
105
+ });
106
+
107
+ return result;
108
+ });
109
+
110
+ const selectElement = (item: any) => {
111
+ emit('update:modelValue', props.itemData ? item[props.itemData] : item);
112
+ };
113
+
114
+ const compareSelectElement = (item: any) => {
115
+ if (props.itemData) {
116
+ return item[props.itemData] === props.modelValue;
117
+ }
118
+
119
+ return deepEqual(props.modelValue, item);
120
+ };
121
+ </script>
122
+
123
+ <style lang="scss">
124
+ @use '../../css/main.scss' as *;
125
+
126
+ .wt-tree {
127
+ padding: var(--spacing-sm);
128
+ background: var(--content-wrapper-color);
129
+ border-radius: var(--border-radius);
130
+ overflow: auto;
131
+
132
+ &__content {
133
+ @extend %wt-scrollbar;
134
+
135
+ display: flex;
136
+ flex-direction: column;
137
+ overflow: auto;
138
+ height: 100%;
139
+ padding-right: var(--spacing-2xs);
140
+ }
141
+
142
+ &__list-content {
143
+ @extend %wt-scrollbar;
144
+
145
+ display: flex;
146
+ flex-direction: column;
147
+ padding-right: var(--spacing-2xs);
148
+ gap: var(--spacing-xs);
149
+ overflow: auto;
150
+ align-items: flex-start;
151
+ height: 100%;
152
+ }
153
+
154
+ &__label-wrapper {
155
+ display: flex;
156
+ align-items: center;
157
+ cursor: pointer;
158
+ padding: 0 var(--spacing-2xs);
159
+ border-radius: var(--border-radius);
160
+ color: var(--wt-tree-item-on);
161
+ transition: var(--transition);
162
+
163
+ &:hover {
164
+ background: var(--wt-tree-item-hover);
165
+ color: var(--wt-tree-item-hover-on);
166
+ }
167
+
168
+ &.active {
169
+ background: var(--wt-tree-item-active);
170
+ color: var(--wt-tree-item-active-on);
171
+ }
172
+ }
173
+
174
+ &__label-icon {
175
+ flex-shrink: 0;
176
+ }
177
+ }
178
+ </style>
@@ -0,0 +1,21 @@
1
+
2
+ :root {
3
+ --wt-tree-item-active: var(--primary-light-color);
4
+ --wt-tree-item-active-on: var(--grey-darken-4);
5
+
6
+ --wt-tree-item: var(--primary-light-color);
7
+ --wt-tree-item-on: var(--grey-darken-1);
8
+
9
+ --wt-tree-item-hover: var(--dp-24-surface-color);
10
+ --wt-tree-item-hover-on: var(--grey-darken-1);
11
+ }
12
+
13
+ :root.theme--dark {
14
+ --wt-tree-item-active: var(--primary-light-color);
15
+ --wt-tree-item-active-on: var(--grey-lighten-4);
16
+
17
+ --wt-tree-item: var(--primary-light-color);
18
+ --wt-tree-item-on: var(--grey-lighten-3);
19
+
20
+ --wt-tree-item-hover-on: var(--grey-lighten-3);
21
+ }
@@ -0,0 +1,4 @@
1
+ export interface WtTreeNestedIcons {
2
+ icon: string;
3
+ hidden: boolean;
4
+ }
@@ -0,0 +1,222 @@
1
+ <template>
2
+ <div class="wt-tree-line">
3
+ <div class="wt-tree-line__icon-wrapper">
4
+ <wt-icon
5
+ v-for="(icon, index) in nestedIcons"
6
+ :key="index"
7
+ :icon="icon.icon"
8
+ :class="{ hidden: icon.hidden }"
9
+ />
10
+ <wt-icon
11
+ v-if="nestedLevel >= 1"
12
+ :icon="lastChild ? 'tree-corner' : 'tree-cross'"
13
+ />
14
+ <wt-icon-btn
15
+ v-if="data[childrenProp] && data[childrenProp].length"
16
+ :icon="collapsed ? 'plus' : 'minus'"
17
+ @click="collapsed = !collapsed"
18
+ />
19
+ </div>
20
+ <div
21
+ :class="{ active: isSelected }"
22
+ class="wt-tree-line__label-wrapper"
23
+ >
24
+ <p
25
+ class="wt-tree-line__label"
26
+ @click="selectElement"
27
+ >
28
+ {{ label }}
29
+ </p>
30
+ <wt-icon
31
+ v-if="isSelected"
32
+ icon="chat-message-status-sent"
33
+ />
34
+ </div>
35
+ </div>
36
+ <wt-expand-transition v-show="!collapsed">
37
+ <div>
38
+ <wt-tree-line
39
+ v-for="(child, index) in data[childrenProp]"
40
+ :key="index"
41
+ :model-value="modelValue"
42
+ :data="child"
43
+ :children-prop="childrenProp"
44
+ :item-label="itemLabel"
45
+ :item-data="itemData"
46
+ :nested-level="nestedLevel + 1"
47
+ :next-element="!!data[childrenProp][index + 1]"
48
+ :nested-icons="displayIcons"
49
+ :last-child="index === data[childrenProp].length - 1"
50
+ @open-parent="onOpenParent"
51
+ @update:model-value="emit('update:modelValue', $event)"
52
+ />
53
+ </div>
54
+ </wt-expand-transition>
55
+ </template>
56
+
57
+ <script setup lang="ts">
58
+ import deepEqual from 'deep-equal';
59
+ import { computed, onMounted, ref, watch } from 'vue';
60
+
61
+ import WtExpandTransition from '../transitions/wt-expand-transition.vue';
62
+ import type { WtTreeNestedIcons } from './types/wt-tree-nested-icons.ts';
63
+
64
+ const props = withDefaults(
65
+ defineProps<{
66
+ modelValue: null | any;
67
+ data: any;
68
+ itemLabel?: string | undefined;
69
+ itemData?: string | undefined;
70
+ childrenProp?: string;
71
+ nestedLevel?: number;
72
+ lastChild?: boolean;
73
+ nestedIcons?: WtTreeNestedIcons[];
74
+ nextElement?: boolean;
75
+ /**
76
+ * 'It's a key in data object, which contains field what display searched elements. By this field, table will be opened to elements with this field value. '
77
+ */
78
+ searchedProp?: string;
79
+ }>(),
80
+ {
81
+ nestedLevel: 0,
82
+ childrenProp: 'children',
83
+ lastChild: false,
84
+ nextElement: false,
85
+ searchedProp: 'searched',
86
+ },
87
+ );
88
+
89
+ const emit = defineEmits<{
90
+ (e: 'openParent'): void;
91
+ (e: 'update:modelValue', value: any): void;
92
+ }>();
93
+
94
+ const label = computed(() =>
95
+ props.itemLabel ? props.data[props.itemLabel] : props.data,
96
+ );
97
+
98
+ const displayIcons = computed(() => {
99
+ const icons = props.nestedIcons ? [...props.nestedIcons] : [];
100
+
101
+ if (props.nestedLevel === 0) {
102
+ return icons;
103
+ }
104
+
105
+ icons.push({
106
+ icon: 'tree-line',
107
+ hidden: !props.nextElement,
108
+ });
109
+
110
+ return icons;
111
+ });
112
+
113
+ const isSelected = computed(() => {
114
+ if (props.itemData) {
115
+ return props.data[props.itemData] === props.modelValue;
116
+ }
117
+
118
+ return deepEqual(props.modelValue, props.data);
119
+ });
120
+
121
+ const selectElement = () => {
122
+ if (props.data[props.childrenProp]?.length) {
123
+ return;
124
+ }
125
+
126
+ emit(
127
+ 'update:modelValue',
128
+ props.itemData ? props.data[props.itemData] : props.data,
129
+ );
130
+ };
131
+
132
+ const collapsed = ref(true);
133
+ const openParent = () => {
134
+ if (props.nestedLevel > 0) {
135
+ emit('openParent');
136
+ }
137
+ };
138
+
139
+ const onOpenParent = () => {
140
+ collapsed.value = false;
141
+ openParent();
142
+ };
143
+
144
+ const hasSearchedElement = (data: Record<string, any>, nestedLevel = 0) => {
145
+ // Check if the object itself has searched
146
+ if (data[props.searchedProp] && nestedLevel) {
147
+ return true;
148
+ }
149
+
150
+ // Check if the object has children
151
+ if (Array.isArray(data[props.childrenProp])) {
152
+ // Iterate through the array
153
+ for (const child of data[props.childrenProp]) {
154
+ // Recursively check nested objects
155
+ if (hasSearchedElement(child, nestedLevel + 1)) {
156
+ return true;
157
+ }
158
+ }
159
+ }
160
+
161
+ // If no match is found, return false
162
+ return false;
163
+ };
164
+
165
+ onMounted(() => {
166
+ if (isSelected.value) {
167
+ openParent();
168
+ }
169
+
170
+ if (props.data[props.searchedProp]) {
171
+ openParent();
172
+ }
173
+ });
174
+
175
+ watch(
176
+ () => props.modelValue,
177
+ () => {
178
+ if (isSelected.value) {
179
+ openParent();
180
+ }
181
+ },
182
+ );
183
+ </script>
184
+
185
+ <style lang="scss">
186
+ @use '../../css/main.scss' as *;
187
+ @use './variables.scss' as *;
188
+
189
+ .wt-tree-line {
190
+ display: flex;
191
+ align-items: flex-start;
192
+
193
+ &__icon-wrapper {
194
+ display: flex;
195
+ }
196
+
197
+ &__label-wrapper {
198
+ display: flex;
199
+ align-items: center;
200
+ cursor: pointer;
201
+ padding: 0 var(--spacing-2xs);
202
+ border-radius: var(--border-radius);
203
+ color: var(--wt-tree-item-on);
204
+ transition: var(--transition);
205
+
206
+ &:hover {
207
+ background: var(--wt-tree-item-hover);
208
+ color: var(--wt-tree-item-hover-on);
209
+ }
210
+
211
+ &.active {
212
+ background: var(--wt-tree-item-active);
213
+ color: var(--wt-tree-item-active-on);
214
+ }
215
+ }
216
+
217
+ &__label {
218
+ @extend %typo-body-1;
219
+ text-wrap: nowrap;
220
+ }
221
+ }
222
+ </style>