@integry/sdk 4.6.17 → 4.6.18

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.
@@ -0,0 +1,1123 @@
1
+ 'use client';
2
+
3
+ import { html } from 'htm/preact';
4
+ import { useState, useEffect } from 'preact/hooks';
5
+ import styles from './styles.module.scss';
6
+
7
+ // Update the TagNode interface to better handle various types
8
+ interface TagNode {
9
+ name?: string; // Step name or field name
10
+ machineName?: string; // Machine-readable name for the step
11
+ appIcon?: string; // URL for the step's app icon
12
+ output?: Record<string, unknown> | unknown[]; // JSON data for step output
13
+ tags?: Record<string, TagNode>; // Nested tags or fields
14
+ [key: string]: unknown; // Allow for additional properties
15
+ }
16
+
17
+ // Define the structure of the entire data object
18
+ interface TagData {
19
+ [key: string]: TagNode;
20
+ }
21
+
22
+ // Define the props for the TagMenu component
23
+ interface TagMenuProps {
24
+ data: TagData;
25
+ onSelect: (tag: TagNode) => void;
26
+ }
27
+
28
+ interface TagListProps {
29
+ tags: Record<string, unknown> | unknown[];
30
+ searchTerm: string;
31
+ level?: number;
32
+ path?: string;
33
+ globalSearchMode?: boolean;
34
+ onSelect?: (tag: Record<string, unknown>) => void;
35
+ activeTab?: string;
36
+ }
37
+
38
+ // Helper function to convert nested tags arrays to objects
39
+ function convertNestedTagsToObject(tags: TagNode[]): Record<string, TagNode> {
40
+ const result: Record<string, TagNode> = {};
41
+
42
+ tags.forEach((tag, index) => {
43
+ const tagKey = tag.machineName || `[${index}]`;
44
+
45
+ if (tag.tags && Array.isArray(tag.tags)) {
46
+ result[tagKey] = {
47
+ ...tag,
48
+ tags: convertNestedTagsToObject(tag.tags),
49
+ };
50
+ } else {
51
+ result[tagKey] = tag;
52
+ }
53
+ });
54
+
55
+ return result;
56
+ }
57
+
58
+ // Add a type guard function to check if a value is a TagNode
59
+ function isTagNode(value: unknown): value is TagNode {
60
+ return (
61
+ value !== null &&
62
+ typeof value === 'object' &&
63
+ !Array.isArray(value) &&
64
+ Object.prototype.hasOwnProperty.call(value, 'machineName')
65
+ );
66
+ }
67
+
68
+ // Update the arrayToObject function with proper typing
69
+ const arrayToObject = (arr: unknown[]): Record<string, unknown> =>
70
+ arr.reduce<Record<string, unknown>>((obj, item, index) => {
71
+ const newObj = { ...obj };
72
+ newObj[`[${index}]`] = item;
73
+ return newObj;
74
+ }, {});
75
+ // Check if output data matches the search term
76
+ const outputMatchesSearch = (output: unknown, term: string): boolean => {
77
+ if (!term || !output) return false;
78
+ const lowerTerm = term.toLowerCase();
79
+
80
+ if (typeof output === 'string') {
81
+ return output.toLowerCase().includes(lowerTerm);
82
+ }
83
+
84
+ if (Array.isArray(output)) {
85
+ return output.some(
86
+ (item) =>
87
+ (typeof item === 'string' && item.toLowerCase().includes(lowerTerm)) ||
88
+ (typeof item === 'object' &&
89
+ item !== null &&
90
+ outputMatchesSearch(item, lowerTerm)),
91
+ );
92
+ }
93
+
94
+ if (typeof output === 'object' && output !== null) {
95
+ return Object.entries(output as Record<string, unknown>).some(
96
+ ([key, val]) => {
97
+ if (key.toLowerCase().includes(lowerTerm)) return true;
98
+
99
+ if (typeof val === 'string') {
100
+ return val.toLowerCase().includes(lowerTerm);
101
+ }
102
+
103
+ if (typeof val === 'object' && val !== null) {
104
+ return outputMatchesSearch(val, lowerTerm);
105
+ }
106
+
107
+ return false;
108
+ },
109
+ );
110
+ }
111
+
112
+ return false;
113
+ };
114
+
115
+ // Function to count matches in a generic object
116
+ const countMatchesInObject = (obj: unknown, term: string): number => {
117
+ if (!obj || typeof obj !== 'object') return 0;
118
+
119
+ let count = 0;
120
+
121
+ // Handle arrays specifically
122
+ if (Array.isArray(obj)) {
123
+ obj.forEach((item) => {
124
+ if (typeof item === 'string' && item.toLowerCase().includes(term)) {
125
+ count += 1;
126
+ } else if (typeof item === 'object' && item !== null) {
127
+ count += countMatchesInObject(item, term);
128
+ }
129
+ });
130
+ return count;
131
+ }
132
+
133
+ Object.entries(obj as Record<string, unknown>).forEach(([key, value]) => {
134
+ if (key.toLowerCase().includes(term)) {
135
+ count += 1;
136
+ }
137
+
138
+ if (typeof value === 'string' && value.toLowerCase().includes(term)) {
139
+ count += 1;
140
+ } else if (typeof value === 'object' && value !== null) {
141
+ count += countMatchesInObject(value, term);
142
+ }
143
+ });
144
+
145
+ return count;
146
+ };
147
+
148
+ // Count matches in a nested structure
149
+ const countSearchMatches = (
150
+ tags: Record<string, TagNode>,
151
+ term: string,
152
+ ): number => {
153
+ let count = 0;
154
+
155
+ Object.entries(tags).forEach(([key, value]) => {
156
+ // Check if the key, name, or machineName matches
157
+ if (
158
+ key.toLowerCase().includes(term) ||
159
+ (isTagNode(value) &&
160
+ value.name &&
161
+ value.name.toLowerCase().includes(term)) ||
162
+ (isTagNode(value) &&
163
+ value.machineName &&
164
+ value.machineName.toLowerCase().includes(term))
165
+ ) {
166
+ count += 1;
167
+ }
168
+
169
+ // Check output data for matches
170
+ if (value.output) {
171
+ count += countMatchesInObject(value.output, term);
172
+ }
173
+
174
+ // Check nested tags
175
+ if (value.tags) {
176
+ count += countSearchMatches(value.tags, term);
177
+ }
178
+ });
179
+
180
+ return count;
181
+ };
182
+
183
+ // Type icon component to render different icons based on data type
184
+ const TypeIcon = ({ value }: { value: unknown }) => {
185
+ // Determine the type of value
186
+ const getTypeIcon = (iconValue: unknown) => {
187
+ if (iconValue === null || iconValue === undefined) {
188
+ return html`
189
+ <svg
190
+ xmlns="http://www.w3.org/2000/svg"
191
+ width="24"
192
+ height="24"
193
+ viewBox="0 0 24 24"
194
+ fill="none"
195
+ stroke="currentColor"
196
+ strokeWidth="2"
197
+ strokeLinecap="round"
198
+ strokeLinejoin="round"
199
+ class="lucide lucide-ban h-4 w-4"
200
+ >
201
+ <circle cx="12" cy="12" r="10"></circle>
202
+ <path d="m4.93 4.93 14.14 14.14"></path>
203
+ </svg>
204
+ `;
205
+ }
206
+
207
+ if (typeof iconValue === 'string') {
208
+ return html`
209
+ <img
210
+ src="data:image/svg+xml,%3csvg%20width='23'%20height='15'%20viewBox='0%200%2023%2015'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20fill-rule='evenodd'%20clip-rule='evenodd'%20d='M4.12408%2010.2498H9.09622L9.5856%2011.7004H9.08601C8.59375%2011.7004%208.24266%2011.8106%208.03275%2012.0311C7.82284%2012.2516%207.71835%2012.536%207.71835%2012.8844C7.71835%2013.24%207.82284%2013.5289%208.03275%2013.7493C8.24266%2013.9698%208.59375%2014.08%209.08601%2014.08H11.8424C12.3347%2014.08%2012.6839%2013.9698%2012.8902%2013.7493C13.0964%2013.5289%2013.2%2013.2444%2013.2%2012.896C13.2%2012.4782%2013.0469%2012.1413%2012.7408%2011.8862C12.5877%2011.7627%2012.2714%2011.7004%2011.792%2011.7004L7.71839%200.0115555L3.29537%200C2.80219%200%202.45111%200.110217%202.2412%200.330662C2.03221%200.551119%201.92679%200.840001%201.92679%201.19556C1.92679%201.55111%202.0322%201.84001%202.2412%202.06045C2.45111%202.28089%202.80219%202.39112%203.29537%202.39112H4.63279L1.41807%2011.7004C0.912084%2011.6853%200.549076%2011.7893%200.329075%2012.0142C0.110001%2012.2382%200%2012.5289%200%2012.8844C0%2013.24%200.104491%2013.5289%200.314408%2013.7493C0.524319%2013.9698%200.875405%2014.08%201.36767%2014.08H4.12408C4.61634%2014.08%204.96743%2013.9698%205.17734%2013.7493C5.38725%2013.5289%205.49175%2013.2444%205.49175%2012.896C5.49175%2012.5404%205.38725%2012.2515%205.17734%2012.0311C4.96743%2011.8107%204.61634%2011.7004%204.12408%2011.7004H3.62449L4.12408%2010.2498ZM6.60013%203.1573L8.24738%207.87022H4.94279L6.60013%203.1573Z'%20fill='%23999999'/%3e%3cpath%20fill-rule='evenodd'%20clip-rule='evenodd'%20d='M19.523%2013.1856V13.7089L21.6596%2013.7089C22.0995%2013.7089%2022.4124%2013.6051%2022.5991%2013.3983C22.7866%2013.1916%2022.88%2012.9248%2022.88%2012.597C22.88%2012.2633%2022.7866%2011.9922%2022.5991%2011.7855C22.4124%2011.5787%2022.0995%2011.4749%2021.6596%2011.4749H21.3485V6.87643C21.3485%205.83759%2021.0347%205.01831%2020.409%204.41862C19.7823%203.8198%2018.9025%203.51953%2017.7685%203.51953C17.3883%203.51953%2016.9639%203.565%2016.4946%203.65594C16.0262%203.74688%2015.5655%203.86871%2015.1143%204.02141C14.7764%204.13723%2014.5534%204.23761%2014.4471%204.32082C14.34%204.40404%2014.257%204.51471%2014.1974%204.65283C14.1377%204.79095%2014.1083%204.95481%2014.1083%205.14355C14.1083%205.47813%2014.193%205.75181%2014.3624%205.96628C14.531%206.18075%2014.7375%206.28799%2014.9813%206.28799C15.1593%206.28799%2015.4082%206.2408%2015.7289%206.14643C16.5897%205.88477%2017.2906%205.75351%2017.8308%205.75351C18.4894%205.75351%2018.9362%205.85903%2019.1713%206.07007C19.4055%206.28027%2019.523%206.55309%2019.523%206.88681V7.42128C18.7988%207.27544%2018.1601%207.20337%2017.6078%207.20337C16.231%207.20337%2015.1515%207.60489%2014.3711%208.40702C13.5906%209.21007%2013.2%2010.08%2013.2%2011.0177C13.2%2011.7872%2013.5059%2012.4907%2014.1169%2013.1264C14.7288%2013.7621%2015.524%2014.0795%2016.5041%2014.0795C16.9552%2014.0795%2017.4565%2014.0049%2018.0088%2013.8565C18.561%2013.7072%2019.0658%2013.4842%2019.523%2013.1856ZM17.5015%209.4477C18.1185%209.4477%2018.7927%209.53521%2019.523%209.70936V10.6685C19.0891%2011.0322%2018.5948%2011.3188%2018.0399%2011.5298C17.485%2011.74%2016.9872%2011.8455%2016.5482%2011.8455C16.0322%2011.8455%2015.6251%2011.7254%2015.3287%2011.4861C15.1679%2011.3548%2015.0875%2011.1841%2015.0875%2010.9739C15.0875%2010.6831%2015.2543%2010.3965%2015.5862%2010.1126C16.1212%209.66989%2016.759%209.4477%2017.5015%209.4477Z'%20fill='%23999999'/%3e%3c/svg%3e"
211
+ alt="alt-icon"
212
+ class="type-icon"
213
+ />
214
+ `;
215
+ }
216
+
217
+ if (typeof iconValue === 'number') {
218
+ return html`
219
+ <img
220
+ src="data:image/svg+xml,%3csvg%20width='15'%20height='8'%20viewBox='0%200%2015%208'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='M3.37681%207.86147H1.83754V1.92808L0%202.49799V1.24633L3.2117%200.0958716H3.37681V7.86147Z'%20fill='%23999999'/%3e%3cpath%20d='M9.39897%207.86147H4.08342V6.80688L6.59206%204.13313C6.93649%203.75675%207.19037%203.4283%207.35371%203.14778C7.5206%202.86727%207.60404%202.60096%207.60404%202.34885C7.60404%202.00443%207.51704%201.73457%207.34305%201.53927C7.16907%201.34043%206.92051%201.241%206.59739%201.241C6.24941%201.241%205.97422%201.36173%205.77183%201.60319C5.57298%201.84109%205.47356%202.15534%205.47356%202.54592H3.92896C3.92896%202.07367%204.04081%201.64225%204.26451%201.25166C4.49176%200.861069%204.81133%200.5557%205.22323%200.335551C5.63512%200.11185%206.10205%200%206.62402%200C7.42295%200%208.04256%200.191743%208.48286%200.57523C8.92671%200.958716%209.14864%201.50021%209.14864%202.19972C9.14864%202.58321%209.04921%202.9738%208.85037%203.37148C8.65152%203.76917%208.31065%204.23255%207.82774%204.76162L6.06477%206.62047H9.39897V7.86147Z'%20fill='%23999999'/%3e%3cpath%20d='M11.3675%203.30224H12.1878C12.5784%203.30224%2012.8677%203.2046%2013.0559%203.0093C13.2441%202.81401%2013.3382%202.5548%2013.3382%202.23168C13.3382%201.91921%2013.2441%201.67598%2013.0559%201.50199C12.8713%201.328%2012.6156%201.241%2012.289%201.241C11.9942%201.241%2011.7475%201.32267%2011.5486%201.48601C11.3498%201.6458%2011.2504%201.85529%2011.2504%202.1145H9.71108C9.71108%201.70971%209.81938%201.34753%2010.036%201.02796C10.2561%200.704834%2010.5615%200.452727%2010.9521%200.271636C11.3462%200.0905454%2011.7794%200%2012.2517%200C13.0719%200%2013.7146%200.197069%2014.1798%200.591208C14.6449%200.981796%2014.8775%201.52152%2014.8775%202.21037C14.8775%202.56545%2014.7692%202.89213%2014.5526%203.19039C14.336%203.48866%2014.0519%203.71769%2013.7004%203.87747C14.1372%204.03371%2014.4621%204.26806%2014.6751%204.58053C14.8917%204.893%2015%205.26229%2015%205.68838C15%206.37724%2014.7479%206.92939%2014.2437%207.34483C13.743%207.76027%2013.079%207.968%2012.2517%207.968C11.4776%207.968%2010.8438%207.76382%2010.3502%207.35548C9.86022%206.94714%209.61521%206.40742%209.61521%205.73632H11.1545C11.1545%206.02748%2011.2628%206.26539%2011.4794%206.45003C11.6995%206.63467%2011.9694%206.72699%2012.289%206.72699C12.6547%206.72699%2012.9405%206.63112%2013.1465%206.43938C13.356%206.24408%2013.4607%205.98665%2013.4607%205.66708C13.4607%204.893%2013.0346%204.50597%2012.1824%204.50597H11.3675V3.30224Z'%20fill='%23999999'/%3e%3c/svg%3e"
221
+ alt="alt-icon"
222
+ class="type-icon"
223
+ />
224
+ `;
225
+ }
226
+
227
+ if (typeof iconValue === 'boolean') {
228
+ // Single icon for all boolean values
229
+ return html`
230
+ <svg
231
+ xmlns="http://www.w3.org/2000/svg"
232
+ width="24"
233
+ height="24"
234
+ viewBox="0 0 24 24"
235
+ fill="none"
236
+ stroke="#71717a"
237
+ strokeWidth="2"
238
+ strokeLinecap="round"
239
+ strokeLinejoin="round"
240
+ class="lucide lucide-toggle-right h-4 w-4"
241
+ >
242
+ <rect width="20" height="12" x="2" y="6" rx="6" ry="6"></rect>
243
+ <circle cx="16" cy="12" r="2"></circle>
244
+ </svg>
245
+ `;
246
+ }
247
+
248
+ if (Array.isArray(iconValue)) {
249
+ return html`
250
+ <svg
251
+ xmlns="http://www.w3.org/2000/svg"
252
+ width="24"
253
+ height="24"
254
+ viewBox="0 0 24 24"
255
+ fill="none"
256
+ stroke="#71717a"
257
+ stroke-width="2"
258
+ stroke-linecap="round"
259
+ stroke-linejoin="round"
260
+ class="lucide lucide-layers"
261
+ >
262
+ <path
263
+ d="M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z"
264
+ />
265
+ <path
266
+ d="M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12"
267
+ />
268
+ <path
269
+ d="M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17"
270
+ />
271
+ </svg>
272
+ `;
273
+ }
274
+
275
+ if (typeof iconValue === 'object') {
276
+ return html`
277
+ <img
278
+ src="data:image/svg+xml,%3csvg%20width='14'%20height='15'%20viewBox='0%200%2014%2015'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='M12.8419%203.40522L7.78708%200.494922C7.3822%200.269922%206.88708%200.269922%206.49684%200.494922L1.44199%203.40522C1.03711%203.63022%200.796875%204.06498%200.796875%204.53022V10.3649C0.796875%2010.8302%201.05175%2011.2497%201.44199%2011.4899L6.49684%2014.3996C6.69197%2014.5197%206.91696%2014.5795%207.14196%2014.5795C7.36696%2014.5795%207.59196%2014.5197%207.78708%2014.3996L12.8419%2011.4746C13.2468%2011.2496%2013.4871%2010.8149%2013.4871%2010.3496V4.53007C13.4871%204.06483%2013.2322%203.63007%2012.8419%203.40507V3.40522ZM7.00723%201.38022C7.05235%201.35034%207.09747%201.35034%207.142%201.35034C7.18712%201.35034%207.23223%201.36557%207.27677%201.38022L11.9421%204.08022L7.14207%206.83992L2.34207%204.08022L7.00723%201.38022ZM1.96693%2010.5899C1.89193%2010.5448%201.83217%2010.4552%201.83217%2010.3649V4.96492L6.63217%207.73992V13.2746L1.96693%2010.5899ZM12.3169%2010.5899L7.65163%2013.2899V7.73992L12.4516%204.96492V10.3496C12.4516%2010.4551%2012.4071%2010.5301%2012.3169%2010.5899L12.3169%2010.5899Z'%20fill='%23999999'/%3e%3c/svg%3e"
279
+ alt="alt-icon"
280
+ class="type-icon"
281
+ />
282
+ `;
283
+ }
284
+
285
+ // Default tag icon for any other type
286
+ return html`
287
+ <svg
288
+ xmlns="http://www.w3.org/2000/svg"
289
+ width="24"
290
+ height="24"
291
+ viewBox="0 0 24 24"
292
+ fill="none"
293
+ stroke="currentColor"
294
+ strokeWidth="2"
295
+ strokeLinecap="round"
296
+ strokeLinejoin="round"
297
+ class="lucide lucide-tag h-4 w-4"
298
+ >
299
+ <path
300
+ d="M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z"
301
+ ></path>
302
+ <circle cx="7.5" cy="7.5" r=".5" fill="currentColor"></circle>
303
+ </svg>
304
+ `;
305
+ };
306
+
307
+ return html`<span class="${styles.tagIcon}">${getTypeIcon(value)}</span>`;
308
+ };
309
+
310
+ const TagList = ({
311
+ tags,
312
+ searchTerm,
313
+ level = 0,
314
+ path = '',
315
+ globalSearchMode = false,
316
+ onSelect = (tag: Record<string, unknown>): void => {
317
+ /* Default empty implementation */
318
+ },
319
+ activeTab = '',
320
+ }: TagListProps) => {
321
+ // Track which nodes are expanded to show nested steps
322
+ const [expandedNodes, setExpandedNodes] = useState<Record<string, boolean>>(
323
+ {},
324
+ );
325
+
326
+ // Track which steps have their output expanded
327
+ const [expandedOutputs, setExpandedOutputs] = useState<
328
+ Record<string, boolean>
329
+ >({});
330
+
331
+ // Handle arrays by converting them to objects with indices as keys
332
+ const processedTags = Array.isArray(tags)
333
+ ? arrayToObject(tags)
334
+ : (tags as Record<string, unknown>);
335
+
336
+ // Update the nodeMatchesSearch function with proper type checking
337
+ const nodeMatchesSearch = (
338
+ key: string,
339
+ node: unknown,
340
+ term: string,
341
+ ): boolean => {
342
+ if (!term) return false;
343
+ const lowerTerm = term.toLowerCase();
344
+
345
+ return (
346
+ key?.toLowerCase().includes(lowerTerm) ||
347
+ (isTagNode(node) &&
348
+ node.name &&
349
+ node.name.toLowerCase().includes(lowerTerm)) ||
350
+ (isTagNode(node) &&
351
+ node.machineName &&
352
+ node.machineName.toLowerCase().includes(lowerTerm)) ||
353
+ false
354
+ );
355
+ };
356
+
357
+ // Update the childrenMatchSearch function with proper type checking
358
+ const childrenMatchSearch = (node: unknown, term: string): boolean => {
359
+ if (!term) return false;
360
+
361
+ // Check if node is an array
362
+ if (Array.isArray(node)) {
363
+ return node.some(
364
+ (item) =>
365
+ (typeof item === 'string' && item.toLowerCase().includes(term)) ||
366
+ (typeof item === 'object' &&
367
+ item !== null &&
368
+ outputMatchesSearch(item, term)),
369
+ );
370
+ }
371
+
372
+ // Check if node has tags
373
+ if (isTagNode(node) && node.tags) {
374
+ return Object.entries(node.tags).some(
375
+ ([childKey, childNode]) =>
376
+ nodeMatchesSearch(childKey, childNode, term) ||
377
+ childrenMatchSearch(childNode, term) ||
378
+ (isTagNode(childNode) &&
379
+ childNode.output &&
380
+ outputMatchesSearch(childNode.output, term)),
381
+ );
382
+ }
383
+
384
+ return false;
385
+ };
386
+
387
+ // Add the isExpandable function with proper type checking
388
+ const isExpandable = (value: unknown): boolean => {
389
+ if (value === null || value === undefined) return false;
390
+
391
+ // Arrays and objects with properties are expandable
392
+ if (Array.isArray(value)) return value.length > 0;
393
+ if (typeof value === 'object') {
394
+ // Check if it's a TagNode with tags
395
+ if (isTagNode(value) && value.tags)
396
+ return Object.keys(value.tags).length > 0;
397
+ // Regular object with properties
398
+ return Object.keys(value as Record<string, unknown>).length > 0;
399
+ }
400
+
401
+ return false;
402
+ };
403
+
404
+ // Initialize expandedNodes for steps with nested steps and handle search expansion
405
+ useEffect(() => {
406
+ const initialExpanded: Record<string, boolean> = {};
407
+ const initialOutputs: Record<string, boolean> = {};
408
+
409
+ Object.entries(processedTags).forEach(([key, value]) => {
410
+ // Auto-expand steps that have nested steps
411
+ if (
412
+ isTagNode(value) &&
413
+ value.tags &&
414
+ Object.keys(value.tags).length > 0
415
+ ) {
416
+ initialExpanded[key] = true;
417
+ }
418
+
419
+ // If in search mode, check if this node or its children match the search
420
+ if (
421
+ searchTerm &&
422
+ (nodeMatchesSearch(key, value, searchTerm) ||
423
+ childrenMatchSearch(value, searchTerm))
424
+ ) {
425
+ initialExpanded[key] = true;
426
+
427
+ // If the node has output and the output matches the search, expand the output too
428
+ if (
429
+ isTagNode(value) &&
430
+ value.output &&
431
+ outputMatchesSearch(value.output, searchTerm)
432
+ ) {
433
+ initialOutputs[key] = true;
434
+ }
435
+ }
436
+ });
437
+
438
+ setExpandedNodes((prev) => ({ ...prev, ...initialExpanded }));
439
+ setExpandedOutputs((prev) => ({ ...prev, ...initialOutputs }));
440
+ }, [processedTags, searchTerm]);
441
+
442
+ // Update the toggleExpand function with proper type checking
443
+ const toggleExpand = (key: string) => {
444
+ // For steps with nested steps, we don't allow collapsing
445
+ const node = processedTags[key];
446
+ if (isTagNode(node) && node.tags && Object.keys(node.tags).length > 0) {
447
+ // Instead, toggle the output expansion
448
+ setExpandedOutputs((prev) => ({
449
+ ...prev,
450
+ [key]: !prev[key],
451
+ }));
452
+ return;
453
+ }
454
+
455
+ // For regular nodes or steps without nested steps, toggle normal expansion
456
+ setExpandedNodes((prev) => ({
457
+ ...prev,
458
+ [key]: !prev[key],
459
+ }));
460
+ };
461
+
462
+ const toggleOutputExpand = (key: string, e: Event) => {
463
+ e.stopPropagation(); // Prevent the click from affecting the parent
464
+ setExpandedOutputs((prev) => ({
465
+ ...prev,
466
+ [key]: !prev[key],
467
+ }));
468
+ };
469
+
470
+ const highlightSearchTerm = (text: string, term: string) => {
471
+ if (!term) return text;
472
+ const regex = new RegExp(`(${term})`, 'gi');
473
+ return text.replace(regex, '<mark>$1</mark>');
474
+ };
475
+
476
+ // Update the filterTags function with proper type checking
477
+ const filterTags = (
478
+ tagsToFilter: Record<string, unknown> | unknown[],
479
+ termToSearch: string,
480
+ pathPrefix: string,
481
+ ): Record<string, unknown> | unknown[] => {
482
+ if (!termToSearch) return tagsToFilter;
483
+
484
+ // Handle arrays
485
+ if (Array.isArray(tagsToFilter)) {
486
+ const filteredArray = tagsToFilter.filter((item, index) => {
487
+ const currentPath = pathPrefix
488
+ ? `${pathPrefix}[${index}]`
489
+ : `[${index}]`;
490
+
491
+ // Check if this item matches the search
492
+ if (
493
+ typeof item === 'string' &&
494
+ item.toLowerCase().includes(termToSearch.toLowerCase())
495
+ ) {
496
+ return true;
497
+ }
498
+
499
+ // Check if this is an object that matches
500
+ if (typeof item === 'object' && item !== null) {
501
+ const filteredItem = filterTags(
502
+ item as Record<string, unknown>,
503
+ termToSearch,
504
+ currentPath,
505
+ );
506
+ return (
507
+ (typeof filteredItem === 'object' &&
508
+ Object.keys(filteredItem as Record<string, unknown>).length >
509
+ 0) ||
510
+ outputMatchesSearch(item, termToSearch)
511
+ );
512
+ }
513
+
514
+ return false;
515
+ });
516
+
517
+ return filteredArray.length > 0 ? arrayToObject(filteredArray) : {};
518
+ }
519
+
520
+ const filteredTags: Record<string, unknown> = {};
521
+ const lowerTerm = termToSearch.toLowerCase();
522
+
523
+ Object.entries(tagsToFilter).forEach(([key, value]) => {
524
+ const currentPath = pathPrefix ? `${pathPrefix}.${key}` : key;
525
+
526
+ // Check if this node matches the search
527
+ const nodeMatches = nodeMatchesSearch(key, value, lowerTerm);
528
+
529
+ // Check if output matches the search
530
+ const outputMatches =
531
+ isTagNode(value) &&
532
+ value.output &&
533
+ outputMatchesSearch(value.output, lowerTerm);
534
+
535
+ // Check if children match the search
536
+ let filteredChildren: Record<string, unknown> = {};
537
+
538
+ // Handle array in output
539
+ if (isTagNode(value) && value.output && Array.isArray(value.output)) {
540
+ const filteredOutput = filterTags(
541
+ value.output,
542
+ termToSearch,
543
+ `${currentPath}.output`,
544
+ );
545
+ const outputMatch =
546
+ typeof filteredOutput === 'object' &&
547
+ Object.keys(filteredOutput as Record<string, unknown>).length > 0;
548
+ if (outputMatch) {
549
+ const clonedNode = { ...value };
550
+ clonedNode.output = value.output; // Keep original array for proper rendering
551
+ filteredTags[key] = clonedNode;
552
+ return;
553
+ }
554
+ }
555
+
556
+ // Handle regular tags
557
+ if (isTagNode(value) && value.tags) {
558
+ filteredChildren = filterTags(
559
+ value.tags,
560
+ termToSearch,
561
+ currentPath,
562
+ ) as Record<string, unknown>;
563
+ }
564
+
565
+ const childrenMatch =
566
+ typeof filteredChildren === 'object' &&
567
+ Object.keys(filteredChildren).length > 0;
568
+
569
+ // Include this node if it matches or has matching children
570
+ if (nodeMatches || outputMatches || childrenMatch) {
571
+ // Clone the node to avoid modifying the original
572
+ const clonedNode = isTagNode(value) ? { ...value } : value;
573
+
574
+ // If children match, include the filtered children
575
+ if (childrenMatch && isTagNode(clonedNode)) {
576
+ clonedNode.tags = filteredChildren as Record<string, TagNode>;
577
+ }
578
+
579
+ filteredTags[key] = clonedNode;
580
+ }
581
+ });
582
+
583
+ return filteredTags;
584
+ };
585
+
586
+ // Only filter if in global search mode, otherwise show all
587
+ const filteredTags = globalSearchMode
588
+ ? filterTags(processedTags, searchTerm, path)
589
+ : processedTags;
590
+
591
+ const extractStepsFromOutputPath = (pathToExtract: string) => {
592
+ const parts = pathToExtract.split('.'); // Split the string by dots
593
+ const outputIndex = parts.lastIndexOf('output'); // Find the last occurrence of 'output'
594
+
595
+ if (outputIndex === -1 || outputIndex === 0) return ''; // Return empty if 'output' is not found or it's at the start
596
+
597
+ // Extract everything from the last step before 'output' to the end
598
+ return parts
599
+ .slice(outputIndex - 1)
600
+ .filter((part) => part !== 'output')
601
+ .join('.');
602
+ };
603
+
604
+ // Simplified rendering to fix syntax issues
605
+ return html`
606
+ <ul style="padding-left: ${1 * 20}px;">
607
+ ${Object.entries(filteredTags as Record<string, unknown>).map(
608
+ ([key, value]) => {
609
+ const isStep = isTagNode(value);
610
+ // We don't use isOutput but keep it for future reference
611
+ const isArrayItem = key.startsWith('[') && key.endsWith(']');
612
+ const hasNestedSteps =
613
+ isStep &&
614
+ isTagNode(value) &&
615
+ value.tags &&
616
+ Object.keys(value.tags).length > 0;
617
+ const hasOutput =
618
+ isTagNode(value) &&
619
+ value.output &&
620
+ typeof value.output === 'object';
621
+ const hasChildren =
622
+ hasNestedSteps || hasOutput || isExpandable(value);
623
+ const isExpanded = expandedNodes[key];
624
+ const isOutputExpanded = expandedOutputs[key];
625
+ const currentPath = path ? `${path}.${key}` : key;
626
+ const nodeMatches =
627
+ searchTerm && nodeMatchesSearch(key, value, searchTerm);
628
+ let text: string;
629
+
630
+ if (isArrayItem) {
631
+ text = key;
632
+ } else if (isTagNode(value) && value.name) {
633
+ text = value.name;
634
+ } else {
635
+ text = key;
636
+ }
637
+
638
+ let tagsParam: Record<string, unknown> | unknown[] = {};
639
+
640
+ if (isTagNode(value)) {
641
+ if (Array.isArray(value.output)) {
642
+ tagsParam = value.output;
643
+ } else {
644
+ tagsParam = value.output as Record<string, unknown>;
645
+ }
646
+ }
647
+
648
+ let tagsParamForOutput: unknown;
649
+
650
+ if (Array.isArray(value)) {
651
+ tagsParamForOutput = value;
652
+ } else if (isTagNode(value) && value.tags) {
653
+ tagsParamForOutput = value.tags;
654
+ } else {
655
+ tagsParamForOutput = value as Record<string, unknown>;
656
+ }
657
+
658
+ return html`
659
+ <li key=${key} class="${nodeMatches ? styles.searchMatch : ''}">
660
+ <div
661
+ onClick=${() => {
662
+ if (isStep) {
663
+ setExpandedOutputs((prev) => ({
664
+ ...prev,
665
+ [key]: !prev[key],
666
+ }));
667
+ } else if (hasChildren) {
668
+ toggleExpand(key);
669
+ } else {
670
+ const tagPath = `${activeTab}.${extractStepsFromOutputPath(
671
+ currentPath,
672
+ )}`;
673
+ onSelect({ currentPath: tagPath, key, value });
674
+ }
675
+ }}
676
+ class="${styles.listItemContent}"
677
+ >
678
+ ${!isStep ? html`<${TypeIcon} value=${value} />` : ''}
679
+ ${isTagNode(value) && value.appIcon
680
+ ? html`<img src=${value.appIcon} alt="App Icon" />`
681
+ : ''}
682
+
683
+ <span
684
+ class="${styles.key}"
685
+ dangerouslySetInnerHTML=${{
686
+ __html: highlightSearchTerm(text, searchTerm),
687
+ }}
688
+ ></span>
689
+
690
+ ${!hasChildren &&
691
+ value !== undefined &&
692
+ (typeof value === 'string' ||
693
+ typeof value === 'number' ||
694
+ typeof value === 'boolean')
695
+ ? html`<span class="${styles.value}">
696
+ ${value ? ' ' : ''}
697
+ <span
698
+ dangerouslySetInnerHTML=${{
699
+ __html: highlightSearchTerm(
700
+ String(value),
701
+ searchTerm,
702
+ ),
703
+ }}
704
+ ></span>
705
+ </span>`
706
+ : ''}
707
+ ${isStep &&
708
+ value !== undefined &&
709
+ isTagNode(value) &&
710
+ value.machineName
711
+ ? html`<span class="${styles.value}">
712
+ ${' '}
713
+ <span
714
+ dangerouslySetInnerHTML=${{
715
+ __html: highlightSearchTerm(
716
+ value.machineName,
717
+ searchTerm,
718
+ ),
719
+ }}
720
+ ></span>
721
+ </span>`
722
+ : ''}
723
+ ${isArrayItem && typeof value === 'object' && !isStep
724
+ ? html`<span class="${styles.value}">
725
+ ${' '}
726
+ <span>
727
+ ${Array.isArray(value)
728
+ ? `Array(${value.length})`
729
+ : `Object${
730
+ value && typeof value === 'object'
731
+ ? ` (${
732
+ Object.keys(
733
+ value as Record<string, unknown>,
734
+ ).length
735
+ } keys)`
736
+ : ''
737
+ }`}
738
+ </span>
739
+ </span>`
740
+ : ''}
741
+ ${isStep && hasOutput
742
+ ? html`<span
743
+ class="${styles.outputIndicator}"
744
+ onClick=${(e: Event) => toggleOutputExpand(key, e)}
745
+ title="Toggle output data"
746
+ >
747
+ <svg
748
+ xmlns="http://www.w3.org/2000/svg"
749
+ width="24"
750
+ height="24"
751
+ viewBox="0 0 24 24"
752
+ fill="none"
753
+ stroke="currentColor"
754
+ strokeWidth="2"
755
+ strokeLinecap="round"
756
+ strokeLinejoin="round"
757
+ style="transform: ${isOutputExpanded
758
+ ? 'rotate(180deg)'
759
+ : 'rotate(0deg)'};
760
+ transition: transform 0.2s;"
761
+ >
762
+ <path d="m6 9 6 6 6-6"></path>
763
+ </svg>
764
+ </span>`
765
+ : ''}
766
+ </div>
767
+
768
+ ${isStep && hasOutput && isOutputExpanded
769
+ ? html`<div class="${styles.outputSection}">
770
+ <${TagList}
771
+ tags=${tagsParam}
772
+ searchTerm=${searchTerm}
773
+ level=${level + 1}
774
+ path=${`${currentPath}.output`}
775
+ globalSearchMode=${globalSearchMode}
776
+ onSelect=${onSelect}
777
+ activeTab=${activeTab}
778
+ />
779
+ </div>`
780
+ : ''}
781
+ ${hasNestedSteps && isExpanded
782
+ ? html`<${TagList}
783
+ tags=${isTagNode(value) ? value.tags || {} : {}}
784
+ searchTerm=${searchTerm}
785
+ level=${level + 1}
786
+ path=${currentPath}
787
+ globalSearchMode=${globalSearchMode}
788
+ onSelect=${onSelect}
789
+ activeTab=${activeTab}
790
+ />`
791
+ : ''}
792
+ ${!isStep && hasChildren && isExpanded
793
+ ? html`<${TagList}
794
+ tags=${tagsParamForOutput}
795
+ searchTerm=${searchTerm}
796
+ level=${level + 1}
797
+ path=${currentPath}
798
+ globalSearchMode=${globalSearchMode}
799
+ onSelect=${onSelect}
800
+ activeTab=${activeTab}
801
+ />`
802
+ : ''}
803
+ </li>
804
+ `;
805
+ },
806
+ )}
807
+ </ul>
808
+ `;
809
+ };
810
+
811
+ // Update the TabMenu component to search across all tabs
812
+ const TabMenu = ({ data, onSelect }: TagMenuProps) => {
813
+ const [activeTab, setActiveTab] = useState<string>(Object.keys(data)[0]);
814
+ const [searchTerm, setSearchTerm] = useState<string>('');
815
+ const [searchResults, setSearchResults] = useState<Record<string, number>>(
816
+ {},
817
+ );
818
+ const [globalSearchMode, setGlobalSearchMode] = useState<boolean>(false);
819
+
820
+ // Search across all tabs and count matches
821
+ useEffect(() => {
822
+ if (!searchTerm) {
823
+ setSearchResults({});
824
+ setGlobalSearchMode(false);
825
+ return;
826
+ }
827
+
828
+ const results: Record<string, number> = {};
829
+
830
+ Object.keys(data).forEach((tabKey) => {
831
+ const matches = countSearchMatches(
832
+ data[tabKey].tags || {},
833
+ searchTerm.toLowerCase(),
834
+ );
835
+ results[tabKey] = matches;
836
+ });
837
+
838
+ setSearchResults(results);
839
+ setGlobalSearchMode(searchTerm.length > 0);
840
+ }, [searchTerm, data]);
841
+
842
+ const getTabIcon = (tabName: string) => {
843
+ const name = tabName.toLowerCase();
844
+
845
+ if (name.includes('trigger')) {
846
+ return html`
847
+ <svg
848
+ xmlns="http://www.w3.org/2000/svg"
849
+ width="16"
850
+ height="16"
851
+ viewBox="0 0 24 24"
852
+ fill="none"
853
+ stroke="currentColor"
854
+ strokeWidth="2"
855
+ strokeLinecap="round"
856
+ strokeLinejoin="round"
857
+ class="mr-1"
858
+ >
859
+ <path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"></path>
860
+ </svg>
861
+ `;
862
+ }
863
+
864
+ if (name.includes('storage') || name.includes('variable')) {
865
+ return html`
866
+ <svg
867
+ xmlns="http://www.w3.org/2000/svg"
868
+ width="16"
869
+ height="16"
870
+ viewBox="0 0 24 24"
871
+ fill="none"
872
+ stroke="currentColor"
873
+ strokeWidth="2"
874
+ strokeLinecap="round"
875
+ strokeLinejoin="round"
876
+ class="mr-1"
877
+ >
878
+ <ellipse cx="12" cy="5" rx="9" ry="3"></ellipse>
879
+ <path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path>
880
+ <path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path>
881
+ </svg>
882
+ `;
883
+ }
884
+
885
+ if (name.includes('form') || name.includes('setup')) {
886
+ // Keep the existing form icon
887
+ return html`
888
+ <svg
889
+ xmlns="http://www.w3.org/2000/svg"
890
+ width="16"
891
+ height="16"
892
+ viewBox="0 0 24 24"
893
+ fill="none"
894
+ stroke="currentColor"
895
+ strokeWidth="2"
896
+ strokeLinecap="round"
897
+ strokeLinejoin="round"
898
+ class="mr-1"
899
+ >
900
+ <rect width="18" height="18" x="3" y="3" rx="2"></rect>
901
+ <path d="M9 9h6"></path>
902
+ <path d="M9 13h6"></path>
903
+ <path d="M9 17h6"></path>
904
+ </svg>
905
+ `;
906
+ }
907
+
908
+ if (name.includes('auth') || name.includes('authorization')) {
909
+ return html`
910
+ <svg
911
+ xmlns="http://www.w3.org/2000/svg"
912
+ width="16"
913
+ height="16"
914
+ viewBox="0 0 24 24"
915
+ fill="none"
916
+ stroke="currentColor"
917
+ strokeWidth="2"
918
+ strokeLinecap="round"
919
+ strokeLinejoin="round"
920
+ class="mr-1"
921
+ >
922
+ <rect width="18" height="11" x="3" y="11" rx="2" ry="2"></rect>
923
+ <path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
924
+ <path d="M12 16v3"></path>
925
+ </svg>
926
+ `;
927
+ }
928
+
929
+ if (name.includes('parameter')) {
930
+ return html`
931
+ <svg
932
+ width="18"
933
+ height="18"
934
+ viewBox="0 0 24 24"
935
+ fill="none"
936
+ xmlns="http://www.w3.org/2000/svg"
937
+ >
938
+ <path
939
+ d="M4 6H14"
940
+ stroke="currentColor"
941
+ stroke-width="2"
942
+ stroke-linecap="round"
943
+ />
944
+ <circle cx="18" cy="6" r="2" stroke="currentColor" stroke-width="2" />
945
+ <path
946
+ d="M4 12H10"
947
+ stroke="currentColor"
948
+ stroke-width="2"
949
+ stroke-linecap="round"
950
+ />
951
+ <circle
952
+ cx="14"
953
+ cy="12"
954
+ r="2"
955
+ stroke="currentColor"
956
+ stroke-width="2"
957
+ />
958
+ <path
959
+ d="M4 18H12"
960
+ stroke="currentColor"
961
+ stroke-width="2"
962
+ stroke-linecap="round"
963
+ />
964
+ <circle
965
+ cx="16"
966
+ cy="18"
967
+ r="2"
968
+ stroke="currentColor"
969
+ stroke-width="2"
970
+ />
971
+ </svg>
972
+ `;
973
+ }
974
+
975
+ // Default icon
976
+ return html`
977
+ <svg
978
+ xmlns="http://www.w3.org/2000/svg"
979
+ width="16"
980
+ height="16"
981
+ viewBox="0 0 24 24"
982
+ fill="none"
983
+ stroke="currentColor"
984
+ strokeWidth="2"
985
+ strokeLinecap="round"
986
+ strokeLinejoin="round"
987
+ class="mr-1"
988
+ >
989
+ <path
990
+ d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"
991
+ ></path>
992
+ </svg>
993
+ `;
994
+ };
995
+
996
+ const renderTabs = () =>
997
+ html`
998
+ <div class="${styles.tabsWrapper}">
999
+ ${Object.keys(data).map(
1000
+ (key) => html`
1001
+ <div
1002
+ class="${styles.tab} ${activeTab === key
1003
+ ? styles.activeTab
1004
+ : ''} ${searchResults[key] > 0 ? styles.tabWithResults : ''}"
1005
+ onClick=${() => setActiveTab(key)}
1006
+ >
1007
+ ${getTabIcon(data[key].name || key)} ${data[key].name || key}
1008
+ ${searchResults[key] > 0
1009
+ ? html`<span class="${styles.resultBadge}"
1010
+ >${searchResults[key]}</span
1011
+ >`
1012
+ : ''}
1013
+ </div>
1014
+ `,
1015
+ )}
1016
+ </div>
1017
+ `;
1018
+ const renderContent = () => {
1019
+ const activeData = data[activeTab];
1020
+ return html`
1021
+ <div class="${styles.tabContent}">
1022
+ <div class="${styles.searchWrapper}">
1023
+ <input
1024
+ type="text"
1025
+ placeholder="Search across all steps and fields..."
1026
+ value=${searchTerm}
1027
+ onInput=${(e: Event) =>
1028
+ setSearchTerm((e.target as HTMLInputElement).value)}
1029
+ />
1030
+ ${searchTerm
1031
+ ? html`
1032
+ <button
1033
+ class="${styles.clearSearch}"
1034
+ onClick=${() => setSearchTerm('')}
1035
+ title="Clear search"
1036
+ >
1037
+ <svg
1038
+ xmlns="http://www.w3.org/2000/svg"
1039
+ width="16"
1040
+ height="16"
1041
+ viewBox="0 0 24 24"
1042
+ fill="none"
1043
+ stroke="currentColor"
1044
+ strokeWidth="2"
1045
+ strokeLinecap="round"
1046
+ strokeLinejoin="round"
1047
+ >
1048
+ <line x1="18" y1="6" x2="6" y2="18"></line>
1049
+ <line x1="6" y1="6" x2="18" y2="18"></line>
1050
+ </svg>
1051
+ </button>
1052
+ `
1053
+ : ''}
1054
+ </div>
1055
+ <div class="${styles.tagsListWrapper}">
1056
+ <${TagList}
1057
+ tags=${activeData.tags}
1058
+ searchTerm=${searchTerm}
1059
+ globalSearchMode=${globalSearchMode}
1060
+ onSelect=${onSelect}
1061
+ activeTab=${data[activeTab].machineName || activeTab}
1062
+ />
1063
+ </div>
1064
+ </div>
1065
+ `;
1066
+ };
1067
+
1068
+ return html`<div>${renderTabs()} ${renderContent()}</div>`;
1069
+ };
1070
+
1071
+ // Update the TagList component parameter types
1072
+
1073
+ // Add this function to convert array-based tags back to object-based tags
1074
+ export function convertTagsToObject(tagData: Record<string, unknown>): TagData {
1075
+ const result: TagData = {};
1076
+
1077
+ Object.keys(tagData).forEach((key) => {
1078
+ const section = tagData[key] as TagNode;
1079
+ const newSection: TagNode = {
1080
+ ...section,
1081
+ };
1082
+
1083
+ // Convert tags array to object
1084
+ if (section.tags && Array.isArray(section.tags)) {
1085
+ const tagsObject: Record<string, TagNode> = {};
1086
+
1087
+ section.tags.forEach((tag: TagNode, index: number) => {
1088
+ // Use machineName as key if available, otherwise use index
1089
+ const tagKey = tag.machineName || `[${index}]`;
1090
+
1091
+ // Recursively convert nested tags
1092
+ if (tag.tags && Array.isArray(tag.tags)) {
1093
+ tagsObject[tagKey] = {
1094
+ ...tag,
1095
+ tags: convertNestedTagsToObject(tag.tags),
1096
+ };
1097
+ } else {
1098
+ tagsObject[tagKey] = tag;
1099
+ }
1100
+ });
1101
+
1102
+ newSection.tags = tagsObject;
1103
+ }
1104
+
1105
+ result[key] = newSection;
1106
+ });
1107
+
1108
+ return result;
1109
+ }
1110
+
1111
+ interface TagsMenuProps {
1112
+ tagsTree: Record<string, unknown>;
1113
+ onSelect: (tag: Record<string, unknown>) => void;
1114
+ }
1115
+
1116
+ export function TagsMenu({ tagsTree, onSelect }: TagsMenuProps) {
1117
+ const modifiedData = convertTagsToObject(tagsTree);
1118
+ return html`
1119
+ <div class="${styles.tagsMenuWrapper}">
1120
+ <${TabMenu} data=${modifiedData} onSelect=${onSelect} />
1121
+ </div>
1122
+ `;
1123
+ }