@industream/flowmaker-flowbox-ui-components 1.0.5 → 1.0.6-poc.0

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.
@@ -24,6 +24,7 @@
24
24
  * @prop onSelectionChange — Called when selection changes (receives full selectedIds array)
25
25
  * @prop onRemove — Called when a single entry is removed from the selection
26
26
  */
27
+ import { untrack } from 'svelte';
27
28
  import { DataCatalogClient } from '@industream/datacatalog-client';
28
29
  import type { CatalogEntry } from '@industream/datacatalog-client/dto';
29
30
  import { DEFAULT_COLUMNS, DEFAULT_SELECTED_COLUMNS_DISPLAY } from './picker-column-helpers';
@@ -126,9 +127,11 @@
126
127
  const uniqueItems = deduplicateEntries(items);
127
128
 
128
129
  if (uniqueItems.length > MAX_ENTRIES) {
129
- // Too many enter server search mode, don't show entries
130
+ // Too many for client-side filtering → server-search mode. Still show the
131
+ // first MAX_ENTRIES as a browsable PREVIEW (with a "not all displayed"
132
+ // warning in TagBrowser) instead of a blank "type to search" screen.
130
133
  tooMany = true;
131
- entries = [];
134
+ entries = uniqueItems.slice(0, MAX_ENTRIES);
132
135
  } else {
133
136
  tooMany = false;
134
137
  entries = uniqueItems;
@@ -152,9 +155,9 @@
152
155
  searchAbort?.abort();
153
156
 
154
157
  if (!query) {
155
- // Empty query in too-many mode → clear results, show hint again
156
- entries = [];
158
+ // Empty query in too-many mode → restore the first-N preview.
157
159
  searching = false;
160
+ loadInitial(dcApiUrl, sourceConnectionId);
158
161
  return;
159
162
  }
160
163
 
@@ -180,6 +183,48 @@
180
183
  searching = false;
181
184
  });
182
185
  }
186
+
187
+ /**
188
+ * Full details of the SELECTED entries, fetched by ID — decoupled from the browse
189
+ * list (`entries`), which is empty in `tooMany` mode and only holds the current
190
+ * page/search otherwise. Without this, the SelectedTags panel can only render
191
+ * selected tags that happen to be in `entries` → on large sources it shows
192
+ * "No tags selected" / loses the selection visually despite a correct count.
193
+ */
194
+ let selectedEntries = $state<CatalogEntry[]>([]);
195
+
196
+ $effect(() => {
197
+ const ids = [...selectedIds]; // reactive dependency
198
+ const url = dcApiUrl; // reactive dependency
199
+
200
+ if (!url || ids.length === 0) {
201
+ selectedEntries = [];
202
+ return;
203
+ }
204
+
205
+ // Reuse already-resolved entries that are still selected; fetch only the missing.
206
+ const idSet = new Set(ids);
207
+ const current = untrack(() => selectedEntries);
208
+ const keep = current.filter((e) => idSet.has(e.id));
209
+ const have = new Set(keep.map((e) => e.id));
210
+ const missing = ids.filter((id) => !have.has(id));
211
+
212
+ if (missing.length === 0) {
213
+ if (keep.length !== current.length) selectedEntries = keep;
214
+ return;
215
+ }
216
+
217
+ // get() auto-switches to POST /catalog-entries/query for large id lists (URI-safe).
218
+ getClient(url).catalogEntries
219
+ .get({ ids: missing })
220
+ .then((res) => {
221
+ const latest = new Set(untrack(() => selectedIds));
222
+ selectedEntries = [...keep, ...(res.items ?? [])].filter((e) => latest.has(e.id));
223
+ })
224
+ .catch(() => {
225
+ // Keep what we have; SelectedTags falls back to the browse list below.
226
+ });
227
+ });
183
228
  </script>
184
229
 
185
230
  <TagBrowser
@@ -197,7 +242,7 @@
197
242
 
198
243
  {#if selectedIds.length > 0}
199
244
  <SelectedTags
200
- {entries}
245
+ entries={selectedEntries.length > 0 ? selectedEntries : entries}
201
246
  bind:selectedIds
202
247
  columns={selectedColumnsDisplay}
203
248
  {onRemove}
@@ -39,9 +39,6 @@
39
39
  const totalCount = $derived(entries.length);
40
40
  const allVisibleSelected = $derived(filtered().length > 0 && filtered().every(e => selectedIds.includes(e.id)));
41
41
 
42
- /** Whether to show the "too many entries" hint instead of the table */
43
- const showTooManyHint = $derived(tooMany && !searchQuery.trim());
44
-
45
42
  function handleSearchInput(e) {
46
43
  searchQuery = e.target.value;
47
44
  if (!onSearch) return;
@@ -109,14 +106,15 @@
109
106
  <svg viewBox="0 0 16 16" width="16" height="16"><path d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1zm-.75 3.5h1.5v5h-1.5zm.75 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2z" fill="currentColor"></path></svg>
110
107
  <span>{error}</span>
111
108
  </div>
112
- {:else if showTooManyHint}
113
- <div class="state-message too-many-state">
114
- <svg viewBox="0 0 16 16" width="16" height="16"><path d="M15.5 13.586L11.742 9.828A5.514 5.514 0 0 0 13 6.5 5.506 5.506 0 0 0 7.5 1 5.506 5.506 0 0 0 2 6.5 5.506 5.506 0 0 0 7.5 12a5.514 5.514 0 0 0 3.328-1.258l3.758 3.758zM3 6.5A4.505 4.505 0 0 1 7.5 2 4.505 4.505 0 0 1 12 6.5 4.505 4.505 0 0 1 7.5 11 4.505 4.505 0 0 1 3 6.5z" fill="currentColor"></path></svg>
115
- <span>Too many entries. Type in the search box to refine.</span>
116
- </div>
117
109
  {:else if entries.length === 0 && !searching}
118
- <div class="state-message empty-state"><span>{emptyMessage}</span></div>
110
+ <div class="state-message empty-state"><span>{tooMany ? 'No matching entries — refine your search.' : emptyMessage}</span></div>
119
111
  {:else}
112
+ {#if tooMany}
113
+ <div class="too-many-banner">
114
+ <svg viewBox="0 0 16 16" width="16" height="16"><path d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1zm-.75 3.5h1.5v5h-1.5zm.75 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2z" fill="currentColor"></path></svg>
115
+ <span>Showing {entries.length} entries — not all are displayed. Use the search box to find others.</span>
116
+ </div>
117
+ {/if}
120
118
  <div class="table-container">
121
119
  <table class="entry-table">
122
120
  <!-- Table headers: one checkbox column + one column per columnDescriptor -->
@@ -203,7 +201,14 @@
203
201
  }
204
202
  .error-state { color: var(--cds-support-error, #fa4d56); }
205
203
  .empty-state { color: var(--cds-text-helper, #8d8d8d); }
206
- .too-many-state { color: var(--cds-support-warning, #f1c21b); }
204
+ .too-many-banner {
205
+ display: flex; align-items: center; gap: 0.5rem;
206
+ padding: 0.5rem 0.75rem;
207
+ background: var(--cds-notification-background-warning, #3d3522);
208
+ border-left: 3px solid var(--cds-support-warning, #f1c21b);
209
+ color: var(--cds-text-primary, #f4f4f4); font-size: 0.75rem;
210
+ }
211
+ .too-many-banner svg { color: var(--cds-support-warning, #f1c21b); flex: 0 0 auto; }
207
212
  .spinner {
208
213
  width: 1rem; height: 1rem;
209
214
  border: 2px solid var(--cds-border-subtle-01, #e0e0e0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@industream/flowmaker-flowbox-ui-components",
3
- "version": "1.0.5",
3
+ "version": "1.0.6-poc.0",
4
4
  "description": "Reusable Svelte components for FlowMaker FlowBox UI",
5
5
  "type": "module",
6
6
  "svelte": "./dist/index.js",
@@ -39,7 +39,8 @@
39
39
  "README.md"
40
40
  ],
41
41
  "scripts": {
42
- "build": "svelte-package --input src"
42
+ "build": "svelte-package --input src",
43
+ "dev": "vite --config vite.dev.config.ts"
43
44
  },
44
45
  "peerDependencies": {
45
46
  "svelte": "^5.0.0",
@@ -24,6 +24,7 @@
24
24
  * @prop onSelectionChange — Called when selection changes (receives full selectedIds array)
25
25
  * @prop onRemove — Called when a single entry is removed from the selection
26
26
  */
27
+ import { untrack } from 'svelte';
27
28
  import { DataCatalogClient } from '@industream/datacatalog-client';
28
29
  import type { CatalogEntry } from '@industream/datacatalog-client/dto';
29
30
  import { DEFAULT_COLUMNS, DEFAULT_SELECTED_COLUMNS_DISPLAY } from './picker-column-helpers';
@@ -126,9 +127,11 @@
126
127
  const uniqueItems = deduplicateEntries(items);
127
128
 
128
129
  if (uniqueItems.length > MAX_ENTRIES) {
129
- // Too many enter server search mode, don't show entries
130
+ // Too many for client-side filtering → server-search mode. Still show the
131
+ // first MAX_ENTRIES as a browsable PREVIEW (with a "not all displayed"
132
+ // warning in TagBrowser) instead of a blank "type to search" screen.
130
133
  tooMany = true;
131
- entries = [];
134
+ entries = uniqueItems.slice(0, MAX_ENTRIES);
132
135
  } else {
133
136
  tooMany = false;
134
137
  entries = uniqueItems;
@@ -152,9 +155,9 @@
152
155
  searchAbort?.abort();
153
156
 
154
157
  if (!query) {
155
- // Empty query in too-many mode → clear results, show hint again
156
- entries = [];
158
+ // Empty query in too-many mode → restore the first-N preview.
157
159
  searching = false;
160
+ loadInitial(dcApiUrl, sourceConnectionId);
158
161
  return;
159
162
  }
160
163
 
@@ -180,6 +183,48 @@
180
183
  searching = false;
181
184
  });
182
185
  }
186
+
187
+ /**
188
+ * Full details of the SELECTED entries, fetched by ID — decoupled from the browse
189
+ * list (`entries`), which is empty in `tooMany` mode and only holds the current
190
+ * page/search otherwise. Without this, the SelectedTags panel can only render
191
+ * selected tags that happen to be in `entries` → on large sources it shows
192
+ * "No tags selected" / loses the selection visually despite a correct count.
193
+ */
194
+ let selectedEntries = $state<CatalogEntry[]>([]);
195
+
196
+ $effect(() => {
197
+ const ids = [...selectedIds]; // reactive dependency
198
+ const url = dcApiUrl; // reactive dependency
199
+
200
+ if (!url || ids.length === 0) {
201
+ selectedEntries = [];
202
+ return;
203
+ }
204
+
205
+ // Reuse already-resolved entries that are still selected; fetch only the missing.
206
+ const idSet = new Set(ids);
207
+ const current = untrack(() => selectedEntries);
208
+ const keep = current.filter((e) => idSet.has(e.id));
209
+ const have = new Set(keep.map((e) => e.id));
210
+ const missing = ids.filter((id) => !have.has(id));
211
+
212
+ if (missing.length === 0) {
213
+ if (keep.length !== current.length) selectedEntries = keep;
214
+ return;
215
+ }
216
+
217
+ // get() auto-switches to POST /catalog-entries/query for large id lists (URI-safe).
218
+ getClient(url).catalogEntries
219
+ .get({ ids: missing })
220
+ .then((res) => {
221
+ const latest = new Set(untrack(() => selectedIds));
222
+ selectedEntries = [...keep, ...(res.items ?? [])].filter((e) => latest.has(e.id));
223
+ })
224
+ .catch(() => {
225
+ // Keep what we have; SelectedTags falls back to the browse list below.
226
+ });
227
+ });
183
228
  </script>
184
229
 
185
230
  <TagBrowser
@@ -197,7 +242,7 @@
197
242
 
198
243
  {#if selectedIds.length > 0}
199
244
  <SelectedTags
200
- {entries}
245
+ entries={selectedEntries.length > 0 ? selectedEntries : entries}
201
246
  bind:selectedIds
202
247
  columns={selectedColumnsDisplay}
203
248
  {onRemove}
@@ -39,9 +39,6 @@
39
39
  const totalCount = $derived(entries.length);
40
40
  const allVisibleSelected = $derived(filtered().length > 0 && filtered().every(e => selectedIds.includes(e.id)));
41
41
 
42
- /** Whether to show the "too many entries" hint instead of the table */
43
- const showTooManyHint = $derived(tooMany && !searchQuery.trim());
44
-
45
42
  function handleSearchInput(e) {
46
43
  searchQuery = e.target.value;
47
44
  if (!onSearch) return;
@@ -109,14 +106,15 @@
109
106
  <svg viewBox="0 0 16 16" width="16" height="16"><path d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1zm-.75 3.5h1.5v5h-1.5zm.75 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2z" fill="currentColor"></path></svg>
110
107
  <span>{error}</span>
111
108
  </div>
112
- {:else if showTooManyHint}
113
- <div class="state-message too-many-state">
114
- <svg viewBox="0 0 16 16" width="16" height="16"><path d="M15.5 13.586L11.742 9.828A5.514 5.514 0 0 0 13 6.5 5.506 5.506 0 0 0 7.5 1 5.506 5.506 0 0 0 2 6.5 5.506 5.506 0 0 0 7.5 12a5.514 5.514 0 0 0 3.328-1.258l3.758 3.758zM3 6.5A4.505 4.505 0 0 1 7.5 2 4.505 4.505 0 0 1 12 6.5 4.505 4.505 0 0 1 7.5 11 4.505 4.505 0 0 1 3 6.5z" fill="currentColor"></path></svg>
115
- <span>Too many entries. Type in the search box to refine.</span>
116
- </div>
117
109
  {:else if entries.length === 0 && !searching}
118
- <div class="state-message empty-state"><span>{emptyMessage}</span></div>
110
+ <div class="state-message empty-state"><span>{tooMany ? 'No matching entries — refine your search.' : emptyMessage}</span></div>
119
111
  {:else}
112
+ {#if tooMany}
113
+ <div class="too-many-banner">
114
+ <svg viewBox="0 0 16 16" width="16" height="16"><path d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1zm-.75 3.5h1.5v5h-1.5zm.75 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2z" fill="currentColor"></path></svg>
115
+ <span>Showing {entries.length} entries — not all are displayed. Use the search box to find others.</span>
116
+ </div>
117
+ {/if}
120
118
  <div class="table-container">
121
119
  <table class="entry-table">
122
120
  <!-- Table headers: one checkbox column + one column per columnDescriptor -->
@@ -203,7 +201,14 @@
203
201
  }
204
202
  .error-state { color: var(--cds-support-error, #fa4d56); }
205
203
  .empty-state { color: var(--cds-text-helper, #8d8d8d); }
206
- .too-many-state { color: var(--cds-support-warning, #f1c21b); }
204
+ .too-many-banner {
205
+ display: flex; align-items: center; gap: 0.5rem;
206
+ padding: 0.5rem 0.75rem;
207
+ background: var(--cds-notification-background-warning, #3d3522);
208
+ border-left: 3px solid var(--cds-support-warning, #f1c21b);
209
+ color: var(--cds-text-primary, #f4f4f4); font-size: 0.75rem;
210
+ }
211
+ .too-many-banner svg { color: var(--cds-support-warning, #f1c21b); flex: 0 0 auto; }
207
212
  .spinner {
208
213
  width: 1rem; height: 1rem;
209
214
  border: 2px solid var(--cds-border-subtle-01, #e0e0e0);