@industream/flowmaker-flowbox-ui-components 0.0.6 → 0.0.9

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/README.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  Reusable Svelte 5 components for FlowMaker FlowBox configuration UIs.
4
4
 
5
+ **Components:**
6
+ - [DCSourceConnection](#dcsourceconnection) - Source connection selector
7
+ - [DCCatalogEntry](#dccatalogentry) - Catalog entry selector
8
+
5
9
  ## Installation
6
10
 
7
11
  ```bash
@@ -175,3 +179,225 @@ interface SourceConnection {
175
179
  [key: string]: any;
176
180
  }
177
181
  ```
182
+
183
+ ---
184
+
185
+ ### DCCatalogEntry
186
+
187
+ A dropdown component that fetches and displays catalog entries from the DataCatalog API. Catalog entries represent data sources with their connection parameters and entry-specific parameters.
188
+
189
+ #### Quick Start
190
+
191
+ ```svelte
192
+ <script>
193
+ import { DCCatalogEntry } from '@industream/flowmaker-flowbox-ui-components';
194
+
195
+ let selectedEntry = $state(null);
196
+ </script>
197
+
198
+ <DCCatalogEntry
199
+ dcapiurl="http://localhost:8002"
200
+ onentryselect={(entry) => selectedEntry = entry}
201
+ />
202
+
203
+ {#if selectedEntry}
204
+ <p>Selected: {selectedEntry.name} ({selectedEntry.dataType})</p>
205
+ {/if}
206
+ ```
207
+
208
+ #### Environment Configuration with `jsonEnv`
209
+
210
+ ```svelte
211
+ <script>
212
+ import { flowMakerBridge } from '@industream/flowmaker-flowbox-ui';
213
+ import { DCCatalogEntry } from '@industream/flowmaker-flowbox-ui-components';
214
+
215
+ const props = $props();
216
+ const { values, jsonEnv } = flowMakerBridge(props);
217
+
218
+ // Get DataCatalog config from environment variables (reactive derived store)
219
+ const dcConfig = jsonEnv('datacatalog');
220
+
221
+ // Optional: data type filter
222
+ const dataTypeFilter = "string";
223
+ </script>
224
+
225
+ <div class="dc-selector-container">
226
+ <div>Data Catalog Entry</div>
227
+
228
+ <DCCatalogEntry
229
+ dcapiurl={$dcConfig?.url}
230
+ datatypefilter={dataTypeFilter}
231
+ initialselection={$values?.catalogEntryId ?? ''}
232
+ onentryselect={(entry) => $values.catalogEntryId = entry.id}
233
+ />
234
+
235
+ {#if $values.catalogEntryId}
236
+ <div class="selection-info">
237
+ <h5>Selected Entry</h5>
238
+ <p><strong>ID:</strong> {$values.catalogEntryId}</p>
239
+ </div>
240
+ {/if}
241
+ </div>
242
+ ```
243
+
244
+ #### Filtering
245
+
246
+ ```svelte
247
+ <!-- Filter by source type -->
248
+ <DCCatalogEntry
249
+ dcapiurl={dcApiUrl}
250
+ sourcetypefilter="InfluxDB2"
251
+ onentryselect={handleSelect}
252
+ />
253
+
254
+ <!-- Filter by data type -->
255
+ <DCCatalogEntry
256
+ dcapiurl={dcApiUrl}
257
+ datatypefilter="string"
258
+ onentryselect={handleSelect}
259
+ />
260
+
261
+ <!-- Filter by name(s) - sent to API -->
262
+ <DCCatalogEntry
263
+ dcapiurl={dcApiUrl}
264
+ namefilter={["Entry1", "Entry2"]}
265
+ onentryselect={handleSelect}
266
+ />
267
+
268
+ <!-- Multiple filters combined -->
269
+ <DCCatalogEntry
270
+ dcapiurl={dcApiUrl}
271
+ sourcetypefilter={["InfluxDB2", "MQTT"]}
272
+ datatypefilter={["string", "number"]}
273
+ onentryselect={handleSelect}
274
+ />
275
+ ```
276
+
277
+ #### Programmatic Methods
278
+
279
+ ```typescript
280
+ // Select by ID
281
+ dcCatalogEntry.select('xxxx-guid-1234-4567');
282
+
283
+ // Select by entry object
284
+ dcCatalogEntry.select(entryObject);
285
+
286
+ // Get current selection
287
+ const selected = dcCatalogEntry.getSelection();
288
+
289
+ // Get all loaded entries (filtered)
290
+ const filtered = dcCatalogEntry.getEntries();
291
+
292
+ // Get all loaded entries (unfiltered)
293
+ const all = dcCatalogEntry.getAllEntries();
294
+ ```
295
+
296
+ ---
297
+
298
+ ## API Reference
299
+
300
+ ### DCCatalogEntry
301
+
302
+ ##### Import
303
+
304
+ ```typescript
305
+ import { DCCatalogEntry, type CatalogEntry, type DataType, type SourceType } from '@industream/flowmaker-flowbox-ui-components';
306
+ ```
307
+
308
+ ##### Props
309
+
310
+ | Prop | Type | Default | Description |
311
+ |------|------|---------|-------------|
312
+ | `id` | `string` | `undefined` | HTML id attribute for the dropdown |
313
+ | `dcapiurl` | `string` | `''` | Base URL of the DataCatalog API |
314
+ | `sourcetypefilter` | `SourceType \| SourceType[] \| null` | `null` | Filter entries by source type(s) (client-side) |
315
+ | `datatypefilter` | `DataType \| DataType[] \| null` | `null` | Filter entries by data type(s) (client-side) |
316
+ | `namefilter` | `string \| string[] \| null` | `null` | Filter entries by name(s) (API query) |
317
+ | `initialselection` | `string \| null` | `null` | Entry ID to auto-select when items load |
318
+ | `onentryselect` | `(entry: CatalogEntry) => void` | `null` | Callback when an entry is selected |
319
+ | `onitemsloaded` | `(entries: CatalogEntry[]) => void` | `null` | Callback when entries are loaded |
320
+
321
+ ##### Exported Methods
322
+
323
+ | Method | Signature | Description |
324
+ |--------|-----------|-------------|
325
+ | `select` | `(idOrEntry: string \| CatalogEntry) => void` | Programmatically select an entry |
326
+ | `getSelection` | `() => CatalogEntry \| null` | Get the currently selected entry |
327
+ | `getEntries` | `() => CatalogEntry[]` | Get all loaded entries (filtered) |
328
+ | `getAllEntries` | `() => CatalogEntry[]` | Get all loaded entries (unfiltered) |
329
+
330
+ ##### CatalogEntry Type
331
+
332
+ The `CatalogEntry` object returned on selection contains the full entry data including:
333
+
334
+ - **sourceConnection**: The parent source connection with all connection parameters (hostname, credentials, etc.)
335
+ - **sourceParams**: Entry-specific parameters that override or extend the connection settings
336
+ - **labels**: User-defined labels for categorization
337
+ - **metadata**: Additional metadata key-value pairs
338
+
339
+ ```typescript
340
+ interface CatalogEntry {
341
+ id: string;
342
+ name: string;
343
+ dataType: string; // e.g., "string", "number", "boolean"
344
+ sourceConnection: {
345
+ id: string;
346
+ name: string;
347
+ sourceType: {
348
+ id: string;
349
+ name: string; // e.g., "InfluxDB2", "MQTT", "DataBridge"
350
+ isProtected: boolean;
351
+ };
352
+ // Connection-level parameters (varies by source type)
353
+ hostname?: string;
354
+ token?: string;
355
+ bucket?: string;
356
+ organization?: string;
357
+ // ... other connection params
358
+ };
359
+ sourceParams: { // Entry-specific parameters
360
+ bucket?: string; // e.g., override bucket for this entry
361
+ // ... other entry-specific params
362
+ };
363
+ labels: Array<{
364
+ id: string;
365
+ name: string;
366
+ }>;
367
+ metadata: Record<string, any>;
368
+ }
369
+ ```
370
+
371
+ **Example CatalogEntry:**
372
+
373
+ ```json
374
+ {
375
+ "id": "57c14290-4dc7-408f-84fa-8fbe568bd506",
376
+ "name": "Influx with myBucket",
377
+ "dataType": "string",
378
+ "sourceConnection": {
379
+ "id": "6e75699f-1e97-493d-9718-930a809b93a4",
380
+ "name": "Influx Localhost",
381
+ "sourceType": {
382
+ "id": "InfluxDB2",
383
+ "name": "InfluxDB2",
384
+ "isProtected": true
385
+ },
386
+ "hostname": "http://influxdb:8086",
387
+ "token": "DQxcrQBORed9pSzH11Ngvrn...",
388
+ "bucket": "test",
389
+ "organization": "industream",
390
+ "ingressHostname": "http://localhost:8086"
391
+ },
392
+ "sourceParams": {
393
+ "bucket": "myBucket"
394
+ },
395
+ "labels": [
396
+ { "id": "1f405a8c-...", "name": "zerzer" },
397
+ { "id": "76432802-...", "name": "Toto" }
398
+ ],
399
+ "metadata": {}
400
+ }
401
+ ```
402
+
403
+ In this example, the `sourceConnection` defines the InfluxDB connection to `test` bucket, but `sourceParams.bucket` overrides it to `myBucket` for this specific catalog entry.
@@ -0,0 +1,217 @@
1
+ <script lang="ts">
2
+ import { DataCatalogClient } from '@industream/datacatalog-client';
3
+ import type { CatalogEntry, DataType, SourceType } from '@industream/datacatalog-client/dto';
4
+
5
+ interface Props {
6
+ id?: string;
7
+ dcapiurl?: string;
8
+ sourcetypefilter?: string | string[] | null; // Source type names like "PostgreSQL", "InfluxDB2"
9
+ datatypefilter?: DataType | DataType[] | null;
10
+ namefilter?: string | string[] | null;
11
+ initialselection?: string | null;
12
+ onentryselect?: (entry: CatalogEntry) => void;
13
+ onitemsloaded?: (entries: CatalogEntry[]) => void;
14
+ }
15
+
16
+ let {
17
+ id = undefined,
18
+ dcapiurl = '',
19
+ sourcetypefilter = null,
20
+ datatypefilter = null,
21
+ namefilter = null,
22
+ initialselection = null,
23
+ onentryselect = null,
24
+ onitemsloaded = null
25
+ }: Props = $props();
26
+
27
+ let catalogEntries = $state<CatalogEntry[]>([]);
28
+ let filteredEntries = $state<CatalogEntry[]>([]);
29
+ let selectedValue = $state('');
30
+ let loading = $state(true);
31
+ let error = $state<string | null>(null);
32
+ let dropdownRef = $state<HTMLElement | null>(null);
33
+
34
+ // Load catalog entries when component mounts or dcapiurl changes
35
+ $effect(() => {
36
+ if (dcapiurl) {
37
+ loadCatalogEntries();
38
+ }
39
+ });
40
+
41
+ // Apply client-side filters when entries or filters change
42
+ $effect(() => {
43
+ filteredEntries = applyFilters(catalogEntries);
44
+ });
45
+
46
+ // Inject styles into shadow DOM to fix trigger-label width
47
+ $effect(() => {
48
+ if (dropdownRef) {
49
+ requestAnimationFrame(() => {
50
+ const sr = (dropdownRef as any)?.shadowRoot;
51
+ if (sr && !sr.querySelector('#dc-entry-fix')) {
52
+ const style = document.createElement('style');
53
+ style.id = 'dc-entry-fix';
54
+ style.textContent = `.cds--list-box__field { display: flex; width: 100%; } .cds--list-box__label { flex: 1; width: 100%; }`;
55
+ sr.appendChild(style);
56
+ }
57
+ });
58
+ }
59
+ });
60
+
61
+ function applyFilters(entries: CatalogEntry[]): CatalogEntry[] {
62
+ let result = entries;
63
+
64
+ // 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
+ if (sourcetypefilter && sourcetypefilter.length > 0) {
67
+ const sourceTypeNames = Array.isArray(sourcetypefilter) ? sourcetypefilter : [sourcetypefilter];
68
+ result = result.filter(entry =>
69
+ entry.sourceConnection?.sourceType?.name &&
70
+ sourceTypeNames.includes(entry.sourceConnection.sourceType.name)
71
+ );
72
+ }
73
+
74
+ // Filter by data type
75
+ if (datatypefilter && datatypefilter.length > 0) {
76
+ const dataTypes = Array.isArray(datatypefilter) ? datatypefilter : [datatypefilter];
77
+ result = result.filter(entry =>
78
+ entry.dataType && dataTypes.includes(entry.dataType)
79
+ );
80
+ }
81
+
82
+ return result;
83
+ }
84
+
85
+ async function loadCatalogEntries() {
86
+ loading = true;
87
+ error = null;
88
+
89
+ try {
90
+ const client = new DataCatalogClient({ baseUrl: dcapiurl });
91
+ const filters: { names?: string[]; sourceTypes?: string[] } = {};
92
+
93
+ // Use name filter for API query if provided
94
+ if (namefilter && namefilter.length > 0) {
95
+ filters.names = Array.isArray(namefilter) ? namefilter : [namefilter];
96
+ }
97
+
98
+ // Use sourceType filter for API query if provided
99
+ if (sourcetypefilter && sourcetypefilter.length > 0) {
100
+ filters.sourceTypes = Array.isArray(sourcetypefilter) ? sourcetypefilter : [sourcetypefilter];
101
+ }
102
+
103
+ const result = await client.catalogEntries.get(filters);
104
+ catalogEntries = result.items || [];
105
+ // Apply filters synchronously so initialselection works
106
+ filteredEntries = applyFilters(catalogEntries);
107
+ console.log('Loaded catalog entries:', catalogEntries);
108
+
109
+ // Auto-select initial selection if provided
110
+ if (initialselection) {
111
+ selectedValue = initialselection;
112
+ const entry = filteredEntries.find(ce => ce.id === initialselection);
113
+ if (entry && onentryselect) {
114
+ onentryselect(entry);
115
+ }
116
+ }
117
+
118
+ onitemsloaded?.(filteredEntries);
119
+ } catch (e) {
120
+ console.error('Failed to load catalog entries:', e);
121
+ error = e.message || 'Failed to load catalog entries';
122
+ catalogEntries = [];
123
+ } finally {
124
+ loading = false;
125
+ }
126
+ }
127
+
128
+ function handleSelect(e: CustomEvent) {
129
+ const selectedId = e.detail?.item?.value || e.target?.value;
130
+ console.log('Dropdown selected event:', e, 'selectedId:', selectedId);
131
+ selectedValue = selectedId;
132
+
133
+ if (selectedId && onentryselect) {
134
+ const selected = filteredEntries.find(ce => ce.id === selectedId);
135
+ console.log('Found selected:', selected);
136
+ if (selected) {
137
+ onentryselect(selected);
138
+ }
139
+ }
140
+
141
+ }
142
+
143
+ // Exposed method to programmatically select a catalog entry
144
+ export function select(idOrEntry: string | CatalogEntry) {
145
+ const id = typeof idOrEntry === 'string' ? idOrEntry : idOrEntry?.id;
146
+ if (!id) return;
147
+
148
+ const entry = filteredEntries.find(ce => ce.id === id);
149
+ if (entry) {
150
+ selectedValue = id;
151
+ if (dropdownRef) {
152
+ dropdownRef.value = id;
153
+ }
154
+ if (onentryselect) {
155
+ onentryselect(entry);
156
+ }
157
+ }
158
+ }
159
+
160
+ // Exposed method to get current selection
161
+ export function getSelection(): CatalogEntry | null {
162
+ return filteredEntries.find(ce => ce.id === selectedValue) || null;
163
+ }
164
+
165
+ // Exposed method to get all loaded entries (filtered)
166
+ export function getEntries(): CatalogEntry[] {
167
+ return filteredEntries;
168
+ }
169
+
170
+ // Exposed method to get all loaded entries (unfiltered)
171
+ export function getAllEntries(): CatalogEntry[] {
172
+ return catalogEntries;
173
+ }
174
+ </script>
175
+
176
+ {#if loading}
177
+ <cds-dropdown {id} label="Catalog Entry" disabled>
178
+ <cds-dropdown-item value="">Loading...</cds-dropdown-item>
179
+ </cds-dropdown>
180
+ {:else if error}
181
+ <cds-dropdown {id} label="Catalog Entry" invalid invalid-text={error}>
182
+ <cds-dropdown-item value="">Error loading entries</cds-dropdown-item>
183
+ </cds-dropdown>
184
+ {:else}
185
+ <cds-dropdown
186
+ {id}
187
+ bind:this={dropdownRef}
188
+ label="Select a catalog entry"
189
+ value={selectedValue}
190
+ oncds-dropdown-selected={handleSelect}
191
+ >
192
+ {#each filteredEntries as entry}
193
+ <cds-dropdown-item value={entry.id}>
194
+ <span style="display: flex; justify-content: space-between; align-items: center; width: 100%;">
195
+ <span style="display: flex; align-items: center; gap: 0.5rem;">
196
+ <span>{entry.name}</span>
197
+ <cds-tag size="sm" type="green">{entry.dataType}</cds-tag>
198
+ </span>
199
+ <span style="display: flex; align-items: center; gap: 0.5rem;">
200
+ <span style="color: var(--cds-text-secondary, #525252); font-size: 0.875rem;">Connection: {entry.sourceConnection?.name || 'Unknown'}</span>
201
+ {#if entry.labels?.length}
202
+ <span style="display: flex; gap: 0.25rem;">
203
+ {#each entry.labels as label}
204
+ <cds-tag size="sm" type="purple">{label.name}</cds-tag>
205
+ {/each}
206
+ </span>
207
+ {/if}
208
+ </span>
209
+ </span>
210
+ </cds-dropdown-item>
211
+ {/each}
212
+ </cds-dropdown>
213
+ {/if}
214
+
215
+ <style>
216
+ /* Styles moved to inline for shadow DOM compatibility */
217
+ </style>
@@ -0,0 +1,19 @@
1
+ import type { CatalogEntry, DataType } from '@industream/datacatalog-client/dto';
2
+ interface Props {
3
+ id?: string;
4
+ dcapiurl?: string;
5
+ sourcetypefilter?: string | string[] | null;
6
+ datatypefilter?: DataType | DataType[] | null;
7
+ namefilter?: string | string[] | null;
8
+ initialselection?: string | null;
9
+ onentryselect?: (entry: CatalogEntry) => void;
10
+ onitemsloaded?: (entries: CatalogEntry[]) => void;
11
+ }
12
+ declare const DCCatalogEntry: import("svelte").Component<Props, {
13
+ select: (idOrEntry: string | CatalogEntry) => void;
14
+ getSelection: () => CatalogEntry | null;
15
+ getEntries: () => CatalogEntry[];
16
+ getAllEntries: () => CatalogEntry[];
17
+ }, "">;
18
+ type DCCatalogEntry = ReturnType<typeof DCCatalogEntry>;
19
+ export default DCCatalogEntry;
package/dist/index.d.ts CHANGED
@@ -1,2 +1,4 @@
1
1
  export { default as DCSourceConnection } from './DCSourceConnection.svelte';
2
2
  export type { SourceConnection } from './DCSourceConnection.svelte';
3
+ export { default as DCCatalogEntry } from './DCCatalogEntry.svelte';
4
+ export type { CatalogEntry, DataType, SourceType } from '@industream/datacatalog-client/dto';
package/dist/index.js CHANGED
@@ -1 +1,2 @@
1
1
  export { default as DCSourceConnection } from './DCSourceConnection.svelte';
2
+ export { default as DCCatalogEntry } from './DCCatalogEntry.svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@industream/flowmaker-flowbox-ui-components",
3
- "version": "0.0.6",
3
+ "version": "0.0.9",
4
4
  "description": "Reusable Svelte components for FlowMaker FlowBox UI",
5
5
  "type": "module",
6
6
  "svelte": "./dist/index.js",
@@ -17,6 +17,11 @@
17
17
  "types": "./dist/DCSourceConnection.svelte.d.ts",
18
18
  "svelte": "./dist/DCSourceConnection.svelte",
19
19
  "import": "./dist/DCSourceConnection.svelte"
20
+ },
21
+ "./DCCatalogEntry.svelte": {
22
+ "types": "./dist/DCCatalogEntry.svelte.d.ts",
23
+ "svelte": "./dist/DCCatalogEntry.svelte",
24
+ "import": "./dist/DCCatalogEntry.svelte"
20
25
  }
21
26
  },
22
27
  "files": [
@@ -29,7 +34,7 @@
29
34
  },
30
35
  "peerDependencies": {
31
36
  "svelte": "^5.0.0",
32
- "@industream/datacatalog-client": "^1.0.0-preview.1"
37
+ "@industream/datacatalog-client": "^1.0.0-preview.2"
33
38
  },
34
39
  "devDependencies": {
35
40
  "svelte": "^5.0.0",
@@ -0,0 +1,217 @@
1
+ <script lang="ts">
2
+ import { DataCatalogClient } from '@industream/datacatalog-client';
3
+ import type { CatalogEntry, DataType, SourceType } from '@industream/datacatalog-client/dto';
4
+
5
+ interface Props {
6
+ id?: string;
7
+ dcapiurl?: string;
8
+ sourcetypefilter?: string | string[] | null; // Source type names like "PostgreSQL", "InfluxDB2"
9
+ datatypefilter?: DataType | DataType[] | null;
10
+ namefilter?: string | string[] | null;
11
+ initialselection?: string | null;
12
+ onentryselect?: (entry: CatalogEntry) => void;
13
+ onitemsloaded?: (entries: CatalogEntry[]) => void;
14
+ }
15
+
16
+ let {
17
+ id = undefined,
18
+ dcapiurl = '',
19
+ sourcetypefilter = null,
20
+ datatypefilter = null,
21
+ namefilter = null,
22
+ initialselection = null,
23
+ onentryselect = null,
24
+ onitemsloaded = null
25
+ }: Props = $props();
26
+
27
+ let catalogEntries = $state<CatalogEntry[]>([]);
28
+ let filteredEntries = $state<CatalogEntry[]>([]);
29
+ let selectedValue = $state('');
30
+ let loading = $state(true);
31
+ let error = $state<string | null>(null);
32
+ let dropdownRef = $state<HTMLElement | null>(null);
33
+
34
+ // Load catalog entries when component mounts or dcapiurl changes
35
+ $effect(() => {
36
+ if (dcapiurl) {
37
+ loadCatalogEntries();
38
+ }
39
+ });
40
+
41
+ // Apply client-side filters when entries or filters change
42
+ $effect(() => {
43
+ filteredEntries = applyFilters(catalogEntries);
44
+ });
45
+
46
+ // Inject styles into shadow DOM to fix trigger-label width
47
+ $effect(() => {
48
+ if (dropdownRef) {
49
+ requestAnimationFrame(() => {
50
+ const sr = (dropdownRef as any)?.shadowRoot;
51
+ if (sr && !sr.querySelector('#dc-entry-fix')) {
52
+ const style = document.createElement('style');
53
+ style.id = 'dc-entry-fix';
54
+ style.textContent = `.cds--list-box__field { display: flex; width: 100%; } .cds--list-box__label { flex: 1; width: 100%; }`;
55
+ sr.appendChild(style);
56
+ }
57
+ });
58
+ }
59
+ });
60
+
61
+ function applyFilters(entries: CatalogEntry[]): CatalogEntry[] {
62
+ let result = entries;
63
+
64
+ // 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
+ if (sourcetypefilter && sourcetypefilter.length > 0) {
67
+ const sourceTypeNames = Array.isArray(sourcetypefilter) ? sourcetypefilter : [sourcetypefilter];
68
+ result = result.filter(entry =>
69
+ entry.sourceConnection?.sourceType?.name &&
70
+ sourceTypeNames.includes(entry.sourceConnection.sourceType.name)
71
+ );
72
+ }
73
+
74
+ // Filter by data type
75
+ if (datatypefilter && datatypefilter.length > 0) {
76
+ const dataTypes = Array.isArray(datatypefilter) ? datatypefilter : [datatypefilter];
77
+ result = result.filter(entry =>
78
+ entry.dataType && dataTypes.includes(entry.dataType)
79
+ );
80
+ }
81
+
82
+ return result;
83
+ }
84
+
85
+ async function loadCatalogEntries() {
86
+ loading = true;
87
+ error = null;
88
+
89
+ try {
90
+ const client = new DataCatalogClient({ baseUrl: dcapiurl });
91
+ const filters: { names?: string[]; sourceTypes?: string[] } = {};
92
+
93
+ // Use name filter for API query if provided
94
+ if (namefilter && namefilter.length > 0) {
95
+ filters.names = Array.isArray(namefilter) ? namefilter : [namefilter];
96
+ }
97
+
98
+ // Use sourceType filter for API query if provided
99
+ if (sourcetypefilter && sourcetypefilter.length > 0) {
100
+ filters.sourceTypes = Array.isArray(sourcetypefilter) ? sourcetypefilter : [sourcetypefilter];
101
+ }
102
+
103
+ const result = await client.catalogEntries.get(filters);
104
+ catalogEntries = result.items || [];
105
+ // Apply filters synchronously so initialselection works
106
+ filteredEntries = applyFilters(catalogEntries);
107
+ console.log('Loaded catalog entries:', catalogEntries);
108
+
109
+ // Auto-select initial selection if provided
110
+ if (initialselection) {
111
+ selectedValue = initialselection;
112
+ const entry = filteredEntries.find(ce => ce.id === initialselection);
113
+ if (entry && onentryselect) {
114
+ onentryselect(entry);
115
+ }
116
+ }
117
+
118
+ onitemsloaded?.(filteredEntries);
119
+ } catch (e) {
120
+ console.error('Failed to load catalog entries:', e);
121
+ error = e.message || 'Failed to load catalog entries';
122
+ catalogEntries = [];
123
+ } finally {
124
+ loading = false;
125
+ }
126
+ }
127
+
128
+ function handleSelect(e: CustomEvent) {
129
+ const selectedId = e.detail?.item?.value || e.target?.value;
130
+ console.log('Dropdown selected event:', e, 'selectedId:', selectedId);
131
+ selectedValue = selectedId;
132
+
133
+ if (selectedId && onentryselect) {
134
+ const selected = filteredEntries.find(ce => ce.id === selectedId);
135
+ console.log('Found selected:', selected);
136
+ if (selected) {
137
+ onentryselect(selected);
138
+ }
139
+ }
140
+
141
+ }
142
+
143
+ // Exposed method to programmatically select a catalog entry
144
+ export function select(idOrEntry: string | CatalogEntry) {
145
+ const id = typeof idOrEntry === 'string' ? idOrEntry : idOrEntry?.id;
146
+ if (!id) return;
147
+
148
+ const entry = filteredEntries.find(ce => ce.id === id);
149
+ if (entry) {
150
+ selectedValue = id;
151
+ if (dropdownRef) {
152
+ dropdownRef.value = id;
153
+ }
154
+ if (onentryselect) {
155
+ onentryselect(entry);
156
+ }
157
+ }
158
+ }
159
+
160
+ // Exposed method to get current selection
161
+ export function getSelection(): CatalogEntry | null {
162
+ return filteredEntries.find(ce => ce.id === selectedValue) || null;
163
+ }
164
+
165
+ // Exposed method to get all loaded entries (filtered)
166
+ export function getEntries(): CatalogEntry[] {
167
+ return filteredEntries;
168
+ }
169
+
170
+ // Exposed method to get all loaded entries (unfiltered)
171
+ export function getAllEntries(): CatalogEntry[] {
172
+ return catalogEntries;
173
+ }
174
+ </script>
175
+
176
+ {#if loading}
177
+ <cds-dropdown {id} label="Catalog Entry" disabled>
178
+ <cds-dropdown-item value="">Loading...</cds-dropdown-item>
179
+ </cds-dropdown>
180
+ {:else if error}
181
+ <cds-dropdown {id} label="Catalog Entry" invalid invalid-text={error}>
182
+ <cds-dropdown-item value="">Error loading entries</cds-dropdown-item>
183
+ </cds-dropdown>
184
+ {:else}
185
+ <cds-dropdown
186
+ {id}
187
+ bind:this={dropdownRef}
188
+ label="Select a catalog entry"
189
+ value={selectedValue}
190
+ oncds-dropdown-selected={handleSelect}
191
+ >
192
+ {#each filteredEntries as entry}
193
+ <cds-dropdown-item value={entry.id}>
194
+ <span style="display: flex; justify-content: space-between; align-items: center; width: 100%;">
195
+ <span style="display: flex; align-items: center; gap: 0.5rem;">
196
+ <span>{entry.name}</span>
197
+ <cds-tag size="sm" type="green">{entry.dataType}</cds-tag>
198
+ </span>
199
+ <span style="display: flex; align-items: center; gap: 0.5rem;">
200
+ <span style="color: var(--cds-text-secondary, #525252); font-size: 0.875rem;">Connection: {entry.sourceConnection?.name || 'Unknown'}</span>
201
+ {#if entry.labels?.length}
202
+ <span style="display: flex; gap: 0.25rem;">
203
+ {#each entry.labels as label}
204
+ <cds-tag size="sm" type="purple">{label.name}</cds-tag>
205
+ {/each}
206
+ </span>
207
+ {/if}
208
+ </span>
209
+ </span>
210
+ </cds-dropdown-item>
211
+ {/each}
212
+ </cds-dropdown>
213
+ {/if}
214
+
215
+ <style>
216
+ /* Styles moved to inline for shadow DOM compatibility */
217
+ </style>
package/src/index.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  export { default as DCSourceConnection } from './DCSourceConnection.svelte';
2
2
  export type { SourceConnection } from './DCSourceConnection.svelte';
3
3
 
4
+ export { default as DCCatalogEntry } from './DCCatalogEntry.svelte';
5
+ export type { CatalogEntry, DataType, SourceType } from '@industream/datacatalog-client/dto';
6
+
4
7
 
5
8