@industream/flowmaker-flowbox-ui-components 0.0.10 → 0.0.12
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/dist/DCCatalogEntry.svelte +135 -26
- package/dist/DCCatalogEntry.svelte.d.ts +10 -3
- package/package.json +1 -1
- package/src/DCCatalogEntry.svelte +135 -26
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
import { DataCatalogClient } from '@industream/datacatalog-client';
|
|
3
3
|
import type { CatalogEntry, DataType, SourceType } from '@industream/datacatalog-client/dto';
|
|
4
4
|
|
|
5
|
+
interface FilterOptions {
|
|
6
|
+
searchtext?: string | null; // Case-insensitive LIKE %text% filter on entry name
|
|
7
|
+
datasetfilter?: string | null; // Filter entries by sourceParams.dataset
|
|
8
|
+
}
|
|
9
|
+
|
|
5
10
|
interface Props {
|
|
6
11
|
id?: string;
|
|
7
12
|
dcapiurl?: string;
|
|
@@ -9,8 +14,9 @@
|
|
|
9
14
|
datatypefilter?: DataType | DataType[] | null;
|
|
10
15
|
namefilter?: string | string[] | null;
|
|
11
16
|
initialselection?: string | null;
|
|
12
|
-
onentryselect?: (entry: CatalogEntry) => void;
|
|
13
|
-
onitemsloaded?: (
|
|
17
|
+
onentryselect?: (entry: CatalogEntry | null) => void;
|
|
18
|
+
onitemsloaded?: (filtered: CatalogEntry[], all: CatalogEntry[]) => void;
|
|
19
|
+
onsearchmiss?: (text: string) => void; // Called when searchtext matches nothing
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
let {
|
|
@@ -21,7 +27,8 @@
|
|
|
21
27
|
namefilter = null,
|
|
22
28
|
initialselection = null,
|
|
23
29
|
onentryselect = null,
|
|
24
|
-
onitemsloaded = null
|
|
30
|
+
onitemsloaded = null,
|
|
31
|
+
onsearchmiss = null
|
|
25
32
|
}: Props = $props();
|
|
26
33
|
|
|
27
34
|
let catalogEntries = $state<CatalogEntry[]>([]);
|
|
@@ -31,18 +38,22 @@
|
|
|
31
38
|
let error = $state<string | null>(null);
|
|
32
39
|
let dropdownRef = $state<HTMLElement | null>(null);
|
|
33
40
|
|
|
34
|
-
//
|
|
41
|
+
// Active dynamic filters (set via setFilters method)
|
|
42
|
+
let activeSearchtext: string | null = null;
|
|
43
|
+
let activeDatasetfilter: string | null = null;
|
|
44
|
+
let autoSelectedId: string | null = null;
|
|
45
|
+
let searchMissText = $state<string | null>(null);
|
|
46
|
+
let multipleMatchText = $state<string | null>(null);
|
|
47
|
+
|
|
48
|
+
// Load catalog entries when component mounts or dcapiurl/sourcetypefilter changes
|
|
35
49
|
$effect(() => {
|
|
50
|
+
// Read sourcetypefilter to establish dependency tracking
|
|
51
|
+
const _sourceTypeFilter = sourcetypefilter;
|
|
36
52
|
if (dcapiurl) {
|
|
37
53
|
loadCatalogEntries();
|
|
38
54
|
}
|
|
39
55
|
});
|
|
40
56
|
|
|
41
|
-
// Apply client-side filters when entries or filters change
|
|
42
|
-
$effect(() => {
|
|
43
|
-
filteredEntries = applyFilters(catalogEntries);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
57
|
// Inject styles into shadow DOM to fix trigger-label width
|
|
47
58
|
$effect(() => {
|
|
48
59
|
if (dropdownRef) {
|
|
@@ -58,11 +69,10 @@
|
|
|
58
69
|
}
|
|
59
70
|
});
|
|
60
71
|
|
|
61
|
-
function
|
|
72
|
+
function applyBaseFilters(entries: CatalogEntry[]): CatalogEntry[] {
|
|
62
73
|
let result = entries;
|
|
63
74
|
|
|
64
75
|
// Filter by source type name (from sourceConnection.sourceType.name)
|
|
65
|
-
// Note: Server-side filtering is now done via API, this is just for additional client-side filtering if needed
|
|
66
76
|
if (sourcetypefilter && sourcetypefilter.length > 0) {
|
|
67
77
|
const sourceTypeNames = Array.isArray(sourcetypefilter) ? sourcetypefilter : [sourcetypefilter];
|
|
68
78
|
result = result.filter(entry =>
|
|
@@ -82,6 +92,54 @@
|
|
|
82
92
|
return result;
|
|
83
93
|
}
|
|
84
94
|
|
|
95
|
+
function applyDynamicFilters(entries: CatalogEntry[], { skipSearch = false } = {}): CatalogEntry[] {
|
|
96
|
+
let result = entries;
|
|
97
|
+
|
|
98
|
+
if (activeDatasetfilter) {
|
|
99
|
+
result = result.filter(entry =>
|
|
100
|
+
entry.sourceParams?.dataset === activeDatasetfilter
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!skipSearch && activeSearchtext?.trim()) {
|
|
105
|
+
const needle = activeSearchtext.trim().toLowerCase();
|
|
106
|
+
result = result.filter(entry =>
|
|
107
|
+
entry.name?.toLowerCase().includes(needle)
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function runFilters() {
|
|
115
|
+
const base = applyBaseFilters(catalogEntries);
|
|
116
|
+
let result = applyDynamicFilters(base);
|
|
117
|
+
|
|
118
|
+
// Search-miss fallback: drop search filter and notify consumer
|
|
119
|
+
if (result.length === 0 && activeSearchtext?.trim()) {
|
|
120
|
+
searchMissText = activeSearchtext.trim();
|
|
121
|
+
multipleMatchText = null;
|
|
122
|
+
result = applyDynamicFilters(base, { skipSearch: true });
|
|
123
|
+
onsearchmiss?.(activeSearchtext.trim());
|
|
124
|
+
} else {
|
|
125
|
+
searchMissText = null;
|
|
126
|
+
multipleMatchText = (result.length > 1 && activeSearchtext?.trim()) ? activeSearchtext.trim() : null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
filteredEntries = result;
|
|
130
|
+
|
|
131
|
+
// Auto-select / auto-clear only when dynamic filters are active
|
|
132
|
+
if (activeSearchtext?.trim() || activeDatasetfilter) {
|
|
133
|
+
if (result.length === 1 && result[0].id !== autoSelectedId) {
|
|
134
|
+
autoSelectedId = result[0].id;
|
|
135
|
+
select(result[0]);
|
|
136
|
+
} else if (result.length !== 1) {
|
|
137
|
+
autoSelectedId = null;
|
|
138
|
+
select(null);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
85
143
|
async function loadCatalogEntries() {
|
|
86
144
|
loading = true;
|
|
87
145
|
error = null;
|
|
@@ -102,9 +160,13 @@
|
|
|
102
160
|
|
|
103
161
|
const result = await client.catalogEntries.get(filters);
|
|
104
162
|
catalogEntries = result.items || [];
|
|
105
|
-
// Apply filters synchronously so initialselection works
|
|
106
|
-
filteredEntries =
|
|
107
|
-
|
|
163
|
+
// Apply base filters synchronously so initialselection works
|
|
164
|
+
filteredEntries = applyBaseFilters(catalogEntries);
|
|
165
|
+
|
|
166
|
+
// Re-apply dynamic filters if any are active
|
|
167
|
+
if (activeSearchtext || activeDatasetfilter) {
|
|
168
|
+
runFilters();
|
|
169
|
+
}
|
|
108
170
|
|
|
109
171
|
// Auto-select initial selection if provided
|
|
110
172
|
if (initialselection) {
|
|
@@ -115,7 +177,7 @@
|
|
|
115
177
|
}
|
|
116
178
|
}
|
|
117
179
|
|
|
118
|
-
onitemsloaded?.(filteredEntries);
|
|
180
|
+
onitemsloaded?.(filteredEntries, catalogEntries);
|
|
119
181
|
} catch (e) {
|
|
120
182
|
console.error('Failed to load catalog entries:', e);
|
|
121
183
|
error = e.message || 'Failed to load catalog entries';
|
|
@@ -127,33 +189,39 @@
|
|
|
127
189
|
|
|
128
190
|
function handleSelect(e: CustomEvent) {
|
|
129
191
|
const selectedId = e.detail?.item?.value || e.target?.value;
|
|
130
|
-
console.log('Dropdown selected event:', e, 'selectedId:', selectedId);
|
|
131
192
|
selectedValue = selectedId;
|
|
132
193
|
|
|
133
194
|
if (selectedId && onentryselect) {
|
|
134
195
|
const selected = filteredEntries.find(ce => ce.id === selectedId);
|
|
135
|
-
console.log('Found selected:', selected);
|
|
136
196
|
if (selected) {
|
|
137
197
|
onentryselect(selected);
|
|
138
198
|
}
|
|
139
199
|
}
|
|
200
|
+
}
|
|
140
201
|
|
|
202
|
+
// Set dynamic filters and re-run filter logic. Call with {} to clear all dynamic filters.
|
|
203
|
+
export function setFilters(opts: FilterOptions) {
|
|
204
|
+
activeSearchtext = opts.searchtext ?? null;
|
|
205
|
+
activeDatasetfilter = opts.datasetfilter ?? null;
|
|
206
|
+
runFilters();
|
|
141
207
|
}
|
|
142
208
|
|
|
143
|
-
//
|
|
144
|
-
export function select(idOrEntry: string | CatalogEntry) {
|
|
209
|
+
// Programmatically select a catalog entry (pass null to clear)
|
|
210
|
+
export function select(idOrEntry: string | CatalogEntry | null) {
|
|
211
|
+
if (!idOrEntry) {
|
|
212
|
+
selectedValue = '';
|
|
213
|
+
if (dropdownRef) dropdownRef.value = '';
|
|
214
|
+
onentryselect?.(null);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
145
217
|
const id = typeof idOrEntry === 'string' ? idOrEntry : idOrEntry?.id;
|
|
146
218
|
if (!id) return;
|
|
147
219
|
|
|
148
220
|
const entry = filteredEntries.find(ce => ce.id === id);
|
|
149
221
|
if (entry) {
|
|
150
222
|
selectedValue = id;
|
|
151
|
-
if (dropdownRef)
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
if (onentryselect) {
|
|
155
|
-
onentryselect(entry);
|
|
156
|
-
}
|
|
223
|
+
if (dropdownRef) dropdownRef.value = id;
|
|
224
|
+
onentryselect?.(entry);
|
|
157
225
|
}
|
|
158
226
|
}
|
|
159
227
|
|
|
@@ -171,6 +239,13 @@
|
|
|
171
239
|
export function getAllEntries(): CatalogEntry[] {
|
|
172
240
|
return catalogEntries;
|
|
173
241
|
}
|
|
242
|
+
|
|
243
|
+
// Reload entries from the API, optionally auto-selecting an entry by ID after reload
|
|
244
|
+
export function reload(selectAfterReload?: string) {
|
|
245
|
+
loadCatalogEntries().then(() => {
|
|
246
|
+
if (selectAfterReload) select(selectAfterReload);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
174
249
|
</script>
|
|
175
250
|
|
|
176
251
|
{#if loading}
|
|
@@ -210,8 +285,42 @@
|
|
|
210
285
|
</cds-dropdown-item>
|
|
211
286
|
{/each}
|
|
212
287
|
</cds-dropdown>
|
|
288
|
+
{#if searchMissText}
|
|
289
|
+
<div class="search-miss search-miss-muted">
|
|
290
|
+
<svg viewBox="0 0 32 32" fill="currentColor" width="14" height="14">
|
|
291
|
+
<path d="M16 2a14 14 0 1014 14A14 14 0 0016 2zm-1.13 5h2.25v12h-2.25zm1.13 17a1.5 1.5 0 111.5-1.5A1.5 1.5 0 0116 24z"/>
|
|
292
|
+
</svg>
|
|
293
|
+
<span>No match for "{searchMissText}" — showing all entries</span>
|
|
294
|
+
</div>
|
|
295
|
+
{/if}
|
|
296
|
+
{#if multipleMatchText}
|
|
297
|
+
<div class="search-miss search-miss-muted">
|
|
298
|
+
<svg viewBox="0 0 32 32" fill="currentColor" width="14" height="14">
|
|
299
|
+
<path d="M16 2a14 14 0 1014 14A14 14 0 0016 2zm-1.13 5h2.25v12h-2.25zm1.13 17a1.5 1.5 0 111.5-1.5A1.5 1.5 0 0116 24z"/>
|
|
300
|
+
</svg>
|
|
301
|
+
<span>Multiple entries found for "{multipleMatchText}" — please make a selection</span>
|
|
302
|
+
</div>
|
|
303
|
+
{/if}
|
|
213
304
|
{/if}
|
|
214
305
|
|
|
215
306
|
<style>
|
|
216
|
-
|
|
307
|
+
.search-miss {
|
|
308
|
+
display: flex;
|
|
309
|
+
align-items: center;
|
|
310
|
+
gap: 0.5rem;
|
|
311
|
+
padding: 0.5rem 0.75rem;
|
|
312
|
+
margin-top: 0.25rem;
|
|
313
|
+
font-size: 0.875rem;
|
|
314
|
+
background: var(--cds-support-warning, #f1c21b);
|
|
315
|
+
color: #161616;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.search-miss-muted {
|
|
319
|
+
background: var(--cds-layer-02, #e0e0e0);
|
|
320
|
+
color: var(--cds-text-secondary, #525252);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.search-miss svg {
|
|
324
|
+
flex-shrink: 0;
|
|
325
|
+
}
|
|
217
326
|
</style>
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { CatalogEntry, DataType } from '@industream/datacatalog-client/dto';
|
|
2
|
+
interface FilterOptions {
|
|
3
|
+
searchtext?: string | null;
|
|
4
|
+
datasetfilter?: string | null;
|
|
5
|
+
}
|
|
2
6
|
interface Props {
|
|
3
7
|
id?: string;
|
|
4
8
|
dcapiurl?: string;
|
|
@@ -6,14 +10,17 @@ interface Props {
|
|
|
6
10
|
datatypefilter?: DataType | DataType[] | null;
|
|
7
11
|
namefilter?: string | string[] | null;
|
|
8
12
|
initialselection?: string | null;
|
|
9
|
-
onentryselect?: (entry: CatalogEntry) => void;
|
|
10
|
-
onitemsloaded?: (
|
|
13
|
+
onentryselect?: (entry: CatalogEntry | null) => void;
|
|
14
|
+
onitemsloaded?: (filtered: CatalogEntry[], all: CatalogEntry[]) => void;
|
|
15
|
+
onsearchmiss?: (text: string) => void;
|
|
11
16
|
}
|
|
12
17
|
declare const DCCatalogEntry: import("svelte").Component<Props, {
|
|
13
|
-
|
|
18
|
+
setFilters: (opts: FilterOptions) => void;
|
|
19
|
+
select: (idOrEntry: string | CatalogEntry | null) => void;
|
|
14
20
|
getSelection: () => CatalogEntry | null;
|
|
15
21
|
getEntries: () => CatalogEntry[];
|
|
16
22
|
getAllEntries: () => CatalogEntry[];
|
|
23
|
+
reload: (selectAfterReload?: string) => void;
|
|
17
24
|
}, "">;
|
|
18
25
|
type DCCatalogEntry = ReturnType<typeof DCCatalogEntry>;
|
|
19
26
|
export default DCCatalogEntry;
|
package/package.json
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
import { DataCatalogClient } from '@industream/datacatalog-client';
|
|
3
3
|
import type { CatalogEntry, DataType, SourceType } from '@industream/datacatalog-client/dto';
|
|
4
4
|
|
|
5
|
+
interface FilterOptions {
|
|
6
|
+
searchtext?: string | null; // Case-insensitive LIKE %text% filter on entry name
|
|
7
|
+
datasetfilter?: string | null; // Filter entries by sourceParams.dataset
|
|
8
|
+
}
|
|
9
|
+
|
|
5
10
|
interface Props {
|
|
6
11
|
id?: string;
|
|
7
12
|
dcapiurl?: string;
|
|
@@ -9,8 +14,9 @@
|
|
|
9
14
|
datatypefilter?: DataType | DataType[] | null;
|
|
10
15
|
namefilter?: string | string[] | null;
|
|
11
16
|
initialselection?: string | null;
|
|
12
|
-
onentryselect?: (entry: CatalogEntry) => void;
|
|
13
|
-
onitemsloaded?: (
|
|
17
|
+
onentryselect?: (entry: CatalogEntry | null) => void;
|
|
18
|
+
onitemsloaded?: (filtered: CatalogEntry[], all: CatalogEntry[]) => void;
|
|
19
|
+
onsearchmiss?: (text: string) => void; // Called when searchtext matches nothing
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
let {
|
|
@@ -21,7 +27,8 @@
|
|
|
21
27
|
namefilter = null,
|
|
22
28
|
initialselection = null,
|
|
23
29
|
onentryselect = null,
|
|
24
|
-
onitemsloaded = null
|
|
30
|
+
onitemsloaded = null,
|
|
31
|
+
onsearchmiss = null
|
|
25
32
|
}: Props = $props();
|
|
26
33
|
|
|
27
34
|
let catalogEntries = $state<CatalogEntry[]>([]);
|
|
@@ -31,18 +38,22 @@
|
|
|
31
38
|
let error = $state<string | null>(null);
|
|
32
39
|
let dropdownRef = $state<HTMLElement | null>(null);
|
|
33
40
|
|
|
34
|
-
//
|
|
41
|
+
// Active dynamic filters (set via setFilters method)
|
|
42
|
+
let activeSearchtext: string | null = null;
|
|
43
|
+
let activeDatasetfilter: string | null = null;
|
|
44
|
+
let autoSelectedId: string | null = null;
|
|
45
|
+
let searchMissText = $state<string | null>(null);
|
|
46
|
+
let multipleMatchText = $state<string | null>(null);
|
|
47
|
+
|
|
48
|
+
// Load catalog entries when component mounts or dcapiurl/sourcetypefilter changes
|
|
35
49
|
$effect(() => {
|
|
50
|
+
// Read sourcetypefilter to establish dependency tracking
|
|
51
|
+
const _sourceTypeFilter = sourcetypefilter;
|
|
36
52
|
if (dcapiurl) {
|
|
37
53
|
loadCatalogEntries();
|
|
38
54
|
}
|
|
39
55
|
});
|
|
40
56
|
|
|
41
|
-
// Apply client-side filters when entries or filters change
|
|
42
|
-
$effect(() => {
|
|
43
|
-
filteredEntries = applyFilters(catalogEntries);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
57
|
// Inject styles into shadow DOM to fix trigger-label width
|
|
47
58
|
$effect(() => {
|
|
48
59
|
if (dropdownRef) {
|
|
@@ -58,11 +69,10 @@
|
|
|
58
69
|
}
|
|
59
70
|
});
|
|
60
71
|
|
|
61
|
-
function
|
|
72
|
+
function applyBaseFilters(entries: CatalogEntry[]): CatalogEntry[] {
|
|
62
73
|
let result = entries;
|
|
63
74
|
|
|
64
75
|
// Filter by source type name (from sourceConnection.sourceType.name)
|
|
65
|
-
// Note: Server-side filtering is now done via API, this is just for additional client-side filtering if needed
|
|
66
76
|
if (sourcetypefilter && sourcetypefilter.length > 0) {
|
|
67
77
|
const sourceTypeNames = Array.isArray(sourcetypefilter) ? sourcetypefilter : [sourcetypefilter];
|
|
68
78
|
result = result.filter(entry =>
|
|
@@ -82,6 +92,54 @@
|
|
|
82
92
|
return result;
|
|
83
93
|
}
|
|
84
94
|
|
|
95
|
+
function applyDynamicFilters(entries: CatalogEntry[], { skipSearch = false } = {}): CatalogEntry[] {
|
|
96
|
+
let result = entries;
|
|
97
|
+
|
|
98
|
+
if (activeDatasetfilter) {
|
|
99
|
+
result = result.filter(entry =>
|
|
100
|
+
entry.sourceParams?.dataset === activeDatasetfilter
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!skipSearch && activeSearchtext?.trim()) {
|
|
105
|
+
const needle = activeSearchtext.trim().toLowerCase();
|
|
106
|
+
result = result.filter(entry =>
|
|
107
|
+
entry.name?.toLowerCase().includes(needle)
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function runFilters() {
|
|
115
|
+
const base = applyBaseFilters(catalogEntries);
|
|
116
|
+
let result = applyDynamicFilters(base);
|
|
117
|
+
|
|
118
|
+
// Search-miss fallback: drop search filter and notify consumer
|
|
119
|
+
if (result.length === 0 && activeSearchtext?.trim()) {
|
|
120
|
+
searchMissText = activeSearchtext.trim();
|
|
121
|
+
multipleMatchText = null;
|
|
122
|
+
result = applyDynamicFilters(base, { skipSearch: true });
|
|
123
|
+
onsearchmiss?.(activeSearchtext.trim());
|
|
124
|
+
} else {
|
|
125
|
+
searchMissText = null;
|
|
126
|
+
multipleMatchText = (result.length > 1 && activeSearchtext?.trim()) ? activeSearchtext.trim() : null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
filteredEntries = result;
|
|
130
|
+
|
|
131
|
+
// Auto-select / auto-clear only when dynamic filters are active
|
|
132
|
+
if (activeSearchtext?.trim() || activeDatasetfilter) {
|
|
133
|
+
if (result.length === 1 && result[0].id !== autoSelectedId) {
|
|
134
|
+
autoSelectedId = result[0].id;
|
|
135
|
+
select(result[0]);
|
|
136
|
+
} else if (result.length !== 1) {
|
|
137
|
+
autoSelectedId = null;
|
|
138
|
+
select(null);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
85
143
|
async function loadCatalogEntries() {
|
|
86
144
|
loading = true;
|
|
87
145
|
error = null;
|
|
@@ -102,9 +160,13 @@
|
|
|
102
160
|
|
|
103
161
|
const result = await client.catalogEntries.get(filters);
|
|
104
162
|
catalogEntries = result.items || [];
|
|
105
|
-
// Apply filters synchronously so initialselection works
|
|
106
|
-
filteredEntries =
|
|
107
|
-
|
|
163
|
+
// Apply base filters synchronously so initialselection works
|
|
164
|
+
filteredEntries = applyBaseFilters(catalogEntries);
|
|
165
|
+
|
|
166
|
+
// Re-apply dynamic filters if any are active
|
|
167
|
+
if (activeSearchtext || activeDatasetfilter) {
|
|
168
|
+
runFilters();
|
|
169
|
+
}
|
|
108
170
|
|
|
109
171
|
// Auto-select initial selection if provided
|
|
110
172
|
if (initialselection) {
|
|
@@ -115,7 +177,7 @@
|
|
|
115
177
|
}
|
|
116
178
|
}
|
|
117
179
|
|
|
118
|
-
onitemsloaded?.(filteredEntries);
|
|
180
|
+
onitemsloaded?.(filteredEntries, catalogEntries);
|
|
119
181
|
} catch (e) {
|
|
120
182
|
console.error('Failed to load catalog entries:', e);
|
|
121
183
|
error = e.message || 'Failed to load catalog entries';
|
|
@@ -127,33 +189,39 @@
|
|
|
127
189
|
|
|
128
190
|
function handleSelect(e: CustomEvent) {
|
|
129
191
|
const selectedId = e.detail?.item?.value || e.target?.value;
|
|
130
|
-
console.log('Dropdown selected event:', e, 'selectedId:', selectedId);
|
|
131
192
|
selectedValue = selectedId;
|
|
132
193
|
|
|
133
194
|
if (selectedId && onentryselect) {
|
|
134
195
|
const selected = filteredEntries.find(ce => ce.id === selectedId);
|
|
135
|
-
console.log('Found selected:', selected);
|
|
136
196
|
if (selected) {
|
|
137
197
|
onentryselect(selected);
|
|
138
198
|
}
|
|
139
199
|
}
|
|
200
|
+
}
|
|
140
201
|
|
|
202
|
+
// Set dynamic filters and re-run filter logic. Call with {} to clear all dynamic filters.
|
|
203
|
+
export function setFilters(opts: FilterOptions) {
|
|
204
|
+
activeSearchtext = opts.searchtext ?? null;
|
|
205
|
+
activeDatasetfilter = opts.datasetfilter ?? null;
|
|
206
|
+
runFilters();
|
|
141
207
|
}
|
|
142
208
|
|
|
143
|
-
//
|
|
144
|
-
export function select(idOrEntry: string | CatalogEntry) {
|
|
209
|
+
// Programmatically select a catalog entry (pass null to clear)
|
|
210
|
+
export function select(idOrEntry: string | CatalogEntry | null) {
|
|
211
|
+
if (!idOrEntry) {
|
|
212
|
+
selectedValue = '';
|
|
213
|
+
if (dropdownRef) dropdownRef.value = '';
|
|
214
|
+
onentryselect?.(null);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
145
217
|
const id = typeof idOrEntry === 'string' ? idOrEntry : idOrEntry?.id;
|
|
146
218
|
if (!id) return;
|
|
147
219
|
|
|
148
220
|
const entry = filteredEntries.find(ce => ce.id === id);
|
|
149
221
|
if (entry) {
|
|
150
222
|
selectedValue = id;
|
|
151
|
-
if (dropdownRef)
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
if (onentryselect) {
|
|
155
|
-
onentryselect(entry);
|
|
156
|
-
}
|
|
223
|
+
if (dropdownRef) dropdownRef.value = id;
|
|
224
|
+
onentryselect?.(entry);
|
|
157
225
|
}
|
|
158
226
|
}
|
|
159
227
|
|
|
@@ -171,6 +239,13 @@
|
|
|
171
239
|
export function getAllEntries(): CatalogEntry[] {
|
|
172
240
|
return catalogEntries;
|
|
173
241
|
}
|
|
242
|
+
|
|
243
|
+
// Reload entries from the API, optionally auto-selecting an entry by ID after reload
|
|
244
|
+
export function reload(selectAfterReload?: string) {
|
|
245
|
+
loadCatalogEntries().then(() => {
|
|
246
|
+
if (selectAfterReload) select(selectAfterReload);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
174
249
|
</script>
|
|
175
250
|
|
|
176
251
|
{#if loading}
|
|
@@ -210,8 +285,42 @@
|
|
|
210
285
|
</cds-dropdown-item>
|
|
211
286
|
{/each}
|
|
212
287
|
</cds-dropdown>
|
|
288
|
+
{#if searchMissText}
|
|
289
|
+
<div class="search-miss search-miss-muted">
|
|
290
|
+
<svg viewBox="0 0 32 32" fill="currentColor" width="14" height="14">
|
|
291
|
+
<path d="M16 2a14 14 0 1014 14A14 14 0 0016 2zm-1.13 5h2.25v12h-2.25zm1.13 17a1.5 1.5 0 111.5-1.5A1.5 1.5 0 0116 24z"/>
|
|
292
|
+
</svg>
|
|
293
|
+
<span>No match for "{searchMissText}" — showing all entries</span>
|
|
294
|
+
</div>
|
|
295
|
+
{/if}
|
|
296
|
+
{#if multipleMatchText}
|
|
297
|
+
<div class="search-miss search-miss-muted">
|
|
298
|
+
<svg viewBox="0 0 32 32" fill="currentColor" width="14" height="14">
|
|
299
|
+
<path d="M16 2a14 14 0 1014 14A14 14 0 0016 2zm-1.13 5h2.25v12h-2.25zm1.13 17a1.5 1.5 0 111.5-1.5A1.5 1.5 0 0116 24z"/>
|
|
300
|
+
</svg>
|
|
301
|
+
<span>Multiple entries found for "{multipleMatchText}" — please make a selection</span>
|
|
302
|
+
</div>
|
|
303
|
+
{/if}
|
|
213
304
|
{/if}
|
|
214
305
|
|
|
215
306
|
<style>
|
|
216
|
-
|
|
307
|
+
.search-miss {
|
|
308
|
+
display: flex;
|
|
309
|
+
align-items: center;
|
|
310
|
+
gap: 0.5rem;
|
|
311
|
+
padding: 0.5rem 0.75rem;
|
|
312
|
+
margin-top: 0.25rem;
|
|
313
|
+
font-size: 0.875rem;
|
|
314
|
+
background: var(--cds-support-warning, #f1c21b);
|
|
315
|
+
color: #161616;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.search-miss-muted {
|
|
319
|
+
background: var(--cds-layer-02, #e0e0e0);
|
|
320
|
+
color: var(--cds-text-secondary, #525252);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.search-miss svg {
|
|
324
|
+
flex-shrink: 0;
|
|
325
|
+
}
|
|
217
326
|
</style>
|