@flowdrop/flowdrop 1.12.0 → 1.14.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.
Files changed (71) hide show
  1. package/README.md +5 -0
  2. package/dist/components/ConfigForm.svelte +1 -0
  3. package/dist/components/ConfigPanel.svelte +7 -1
  4. package/dist/components/NodeSwapPicker.svelte +5 -1
  5. package/dist/components/PipelineStatus.svelte +11 -2
  6. package/dist/components/SchemaForm.svelte +1 -0
  7. package/dist/components/SettingsPanel.svelte +5 -1
  8. package/dist/components/WorkflowEditor.svelte +5 -1
  9. package/dist/components/chat/AIChatPanel.svelte +1 -5
  10. package/dist/components/form/FormAutocomplete.svelte +69 -15
  11. package/dist/components/form/FormField.svelte +21 -0
  12. package/dist/components/form/FormFieldLight.svelte +1 -0
  13. package/dist/components/interrupt/ChoicePrompt.svelte +5 -1
  14. package/dist/components/interrupt/InterruptBubble.svelte +75 -17
  15. package/dist/components/interrupt/InterruptBubble.svelte.d.ts +11 -0
  16. package/dist/components/playground/ChatBubble.svelte +287 -0
  17. package/dist/components/playground/ChatBubble.svelte.d.ts +10 -0
  18. package/dist/components/playground/ChatInput.svelte +11 -5
  19. package/dist/components/playground/ControlPanel.svelte +42 -29
  20. package/dist/components/playground/ExecutionConsole.svelte +5 -1
  21. package/dist/components/playground/ExecutionConsole.svelte.d.ts +2 -0
  22. package/dist/components/playground/ExecutionList.svelte +7 -2
  23. package/dist/components/playground/HierarchyTrail.svelte +88 -0
  24. package/dist/components/playground/HierarchyTrail.svelte.d.ts +7 -0
  25. package/dist/components/playground/LogRow.svelte +179 -0
  26. package/dist/components/playground/LogRow.svelte.d.ts +8 -0
  27. package/dist/components/playground/MessageBubble.stories.svelte +89 -0
  28. package/dist/components/playground/MessageBubble.svelte +23 -738
  29. package/dist/components/playground/MessageBubble.svelte.d.ts +3 -11
  30. package/dist/components/playground/MessageCard.svelte +107 -0
  31. package/dist/components/playground/MessageCard.svelte.d.ts +10 -0
  32. package/dist/components/playground/MessageMarkdown.svelte +170 -0
  33. package/dist/components/playground/MessageMarkdown.svelte.d.ts +7 -0
  34. package/dist/components/playground/MessageNotice.svelte +121 -0
  35. package/dist/components/playground/MessageNotice.svelte.d.ts +9 -0
  36. package/dist/components/playground/MessageStream.svelte +215 -10
  37. package/dist/components/playground/MessageStream.svelte.d.ts +5 -0
  38. package/dist/components/playground/MessageTagChip.svelte +117 -0
  39. package/dist/components/playground/MessageTagChip.svelte.d.ts +7 -0
  40. package/dist/components/playground/MessageTagStrip.svelte +37 -0
  41. package/dist/components/playground/MessageTagStrip.svelte.d.ts +7 -0
  42. package/dist/components/playground/PipelineKanbanView.svelte +40 -11
  43. package/dist/components/playground/PipelinePanel.svelte +5 -1
  44. package/dist/components/playground/PipelineTableView.svelte +20 -6
  45. package/dist/components/playground/Playground.svelte +84 -22
  46. package/dist/components/playground/PlaygroundStudio.svelte +99 -7
  47. package/dist/components/playground/messageDisplay.d.ts +19 -0
  48. package/dist/components/playground/messageDisplay.js +62 -0
  49. package/dist/components/playground/pipelineViewUtils.svelte.js +11 -4
  50. package/dist/form/autocomplete.d.ts +1 -0
  51. package/dist/form/autocomplete.js +1 -0
  52. package/dist/form/index.d.ts +17 -0
  53. package/dist/form/index.js +19 -0
  54. package/dist/messages/defaults.d.ts +5 -0
  55. package/dist/messages/defaults.js +6 -0
  56. package/dist/openapi/v1/openapi.yaml +6403 -0
  57. package/dist/schemas/v1/workflow.schema.json +46 -1
  58. package/dist/services/categoriesApi.d.ts +2 -1
  59. package/dist/services/categoriesApi.js +5 -3
  60. package/dist/services/playgroundService.d.ts +23 -4
  61. package/dist/services/playgroundService.js +22 -9
  62. package/dist/services/portConfigApi.d.ts +2 -1
  63. package/dist/services/portConfigApi.js +5 -3
  64. package/dist/stores/playgroundStore.svelte.d.ts +22 -1
  65. package/dist/stores/playgroundStore.svelte.js +109 -32
  66. package/dist/svelte-app.d.ts +1 -0
  67. package/dist/svelte-app.js +5 -5
  68. package/dist/types/index.d.ts +13 -0
  69. package/dist/types/playground.d.ts +112 -2
  70. package/dist/types/playground.js +14 -0
  71. package/package.json +12 -1
@@ -0,0 +1,287 @@
1
+ <!--
2
+ ChatBubble — avatar + bubble layout for user/assistant/system messages.
3
+ Markdown typography comes from MessageMarkdown; user-bubble overrides
4
+ (primary-bg) are scoped here.
5
+ -->
6
+
7
+ <script lang="ts">
8
+ import Icon from '@iconify/svelte';
9
+ import type { PlaygroundMessage } from '../../types/playground.js';
10
+ import HierarchyTrail from './HierarchyTrail.svelte';
11
+ import MessageTagStrip from './MessageTagStrip.svelte';
12
+ import MessageMarkdown from './MessageMarkdown.svelte';
13
+ import { formatDuration, formatTimestamp, getRoleIcon, getRoleLabel } from './messageDisplay.js';
14
+ import { m } from '../../messages/index.js';
15
+
16
+ interface Props {
17
+ message: PlaygroundMessage;
18
+ showTimestamp?: boolean;
19
+ isLast?: boolean;
20
+ enableMarkdown?: boolean;
21
+ }
22
+
23
+ let { message, showTimestamp = true, isLast = false, enableMarkdown = true }: Props = $props();
24
+
25
+ const hierarchy = $derived(message.hierarchy ?? []);
26
+ const tags = $derived(message.tags ?? []);
27
+ const roleLabel = $derived(getRoleLabel(message, m().playground.roles));
28
+ const hasFooter = $derived(
29
+ message.metadata?.duration !== undefined || !!message.nodeId || tags.length > 0
30
+ );
31
+ </script>
32
+
33
+ <article
34
+ class="message-bubble"
35
+ class:message-bubble--user={message.role === 'user'}
36
+ class:message-bubble--assistant={message.role === 'assistant'}
37
+ class:message-bubble--system={message.role === 'system'}
38
+ class:message-bubble--last={isLast}
39
+ aria-label="{roleLabel} message"
40
+ >
41
+ <div class="message-bubble__avatar" aria-hidden="true">
42
+ <Icon icon={getRoleIcon(message.role)} />
43
+ </div>
44
+
45
+ <div class="message-bubble__content">
46
+ <div class="message-bubble__header">
47
+ <span class="message-bubble__role">{roleLabel}</span>
48
+ {#if showTimestamp}
49
+ <time
50
+ class="message-bubble__timestamp"
51
+ datetime={message.timestamp}
52
+ aria-label="sent at {formatTimestamp(message.timestamp)}"
53
+ >{formatTimestamp(message.timestamp)}</time
54
+ >
55
+ {/if}
56
+ </div>
57
+
58
+ {#if hierarchy.length > 0}
59
+ <div class="message-bubble__hierarchy">
60
+ <HierarchyTrail items={hierarchy} />
61
+ </div>
62
+ {/if}
63
+
64
+ <MessageMarkdown content={message.content} {enableMarkdown} />
65
+
66
+ {#if hasFooter}
67
+ <div class="message-bubble__footer">
68
+ {#if message.nodeId}
69
+ <span
70
+ class="message-bubble__node"
71
+ title={m().playground.messageTooltips.nodeId({ id: message.nodeId })}
72
+ >
73
+ <Icon icon="mdi:vector-square" aria-hidden="true" />
74
+ via {message.metadata?.nodeLabel ?? message.nodeId}
75
+ </span>
76
+ {/if}
77
+ {#if message.metadata?.duration !== undefined}
78
+ <span
79
+ class="message-bubble__duration"
80
+ title={m().playground.messageTooltips.executionDuration}
81
+ aria-label="execution duration {formatDuration(message.metadata.duration)}"
82
+ >
83
+ <Icon icon="mdi:timer-outline" aria-hidden="true" />
84
+ {formatDuration(message.metadata.duration)}
85
+ </span>
86
+ {/if}
87
+ <MessageTagStrip {tags} />
88
+ </div>
89
+ {/if}
90
+ </div>
91
+ </article>
92
+
93
+ <style>
94
+ .message-bubble {
95
+ display: flex;
96
+ gap: var(--fd-space-sm);
97
+ padding: 2px var(--fd-space-xl);
98
+ margin-bottom: 2px;
99
+ align-items: flex-end;
100
+ /* fd-fade-in + reduced-motion guard live in MessageStream.svelte */
101
+ animation: fd-fade-in 0.18s ease-out;
102
+ }
103
+
104
+ .message-bubble--user {
105
+ flex-direction: row-reverse;
106
+ }
107
+
108
+ .message-bubble--last {
109
+ margin-bottom: var(--fd-space-xl);
110
+ }
111
+
112
+ .message-bubble__avatar {
113
+ flex-shrink: 0;
114
+ width: 1.875rem;
115
+ height: 1.875rem;
116
+ display: flex;
117
+ align-items: center;
118
+ justify-content: center;
119
+ border-radius: var(--fd-radius-full);
120
+ font-size: 1rem;
121
+ }
122
+
123
+ .message-bubble--user .message-bubble__avatar {
124
+ background-color: var(--fd-primary);
125
+ color: var(--fd-primary-foreground);
126
+ }
127
+
128
+ .message-bubble--assistant .message-bubble__avatar {
129
+ background-color: var(--fd-secondary);
130
+ color: var(--fd-secondary-foreground);
131
+ border: 1px solid var(--fd-border);
132
+ }
133
+
134
+ .message-bubble--system .message-bubble__avatar {
135
+ background-color: var(--fd-muted);
136
+ color: var(--fd-muted-foreground);
137
+ }
138
+
139
+ .message-bubble__content {
140
+ min-width: 0;
141
+ max-width: 78%;
142
+ padding: var(--fd-space-sm) var(--fd-space-md);
143
+ border-radius: var(--fd-radius-2xl);
144
+ }
145
+
146
+ .message-bubble--user .message-bubble__content {
147
+ background-color: var(--fd-primary);
148
+ color: var(--fd-primary-foreground);
149
+ border-bottom-right-radius: var(--fd-radius-sm);
150
+ }
151
+
152
+ .message-bubble--assistant .message-bubble__content {
153
+ background-color: var(--fd-card);
154
+ border: 1px solid var(--fd-border);
155
+ color: var(--fd-card-foreground);
156
+ box-shadow:
157
+ 0 1px 3px 0 oklch(0% 0 0 / 0.06),
158
+ 0 1px 2px -1px oklch(0% 0 0 / 0.04);
159
+ border-bottom-left-radius: var(--fd-radius-sm);
160
+ }
161
+
162
+ .message-bubble--system .message-bubble__content {
163
+ background-color: var(--fd-muted);
164
+ border: 1px solid var(--fd-border);
165
+ color: var(--fd-muted-foreground);
166
+ font-size: var(--fd-text-sm);
167
+ max-width: 88%;
168
+ }
169
+
170
+ .message-bubble__header {
171
+ display: flex;
172
+ align-items: center;
173
+ gap: var(--fd-space-xs);
174
+ margin-bottom: var(--fd-space-3xs);
175
+ }
176
+
177
+ .message-bubble--user .message-bubble__header {
178
+ flex-direction: row-reverse;
179
+ }
180
+
181
+ .message-bubble__role {
182
+ font-weight: 600;
183
+ font-size: var(--fd-text-xs);
184
+ text-transform: uppercase;
185
+ letter-spacing: 0.05em;
186
+ }
187
+
188
+ .message-bubble--user .message-bubble__role {
189
+ color: var(--fd-primary-foreground);
190
+ opacity: 0.75;
191
+ }
192
+
193
+ .message-bubble--assistant .message-bubble__role,
194
+ .message-bubble--system .message-bubble__role {
195
+ color: var(--fd-muted-foreground);
196
+ }
197
+
198
+ .message-bubble__timestamp {
199
+ font-size: 0.6875rem;
200
+ font-family: var(--fd-font-mono);
201
+ opacity: 0.55;
202
+ }
203
+
204
+ .message-bubble--user .message-bubble__timestamp {
205
+ color: var(--fd-primary-foreground);
206
+ }
207
+
208
+ .message-bubble--assistant .message-bubble__timestamp {
209
+ color: var(--fd-muted-foreground);
210
+ }
211
+
212
+ .message-bubble__hierarchy {
213
+ margin: var(--fd-space-3xs) 0 var(--fd-space-xs);
214
+ }
215
+
216
+ /* Override markdown styling on the primary-bg user bubble */
217
+ .message-bubble--user :global(.message-markdown code) {
218
+ background-color: color-mix(in srgb, var(--fd-primary-foreground) 18%, transparent);
219
+ color: var(--fd-primary-foreground);
220
+ }
221
+
222
+ .message-bubble--user :global(.message-markdown pre) {
223
+ background-color: rgb(0 0 0 / 0.25);
224
+ color: var(--fd-primary-foreground);
225
+ }
226
+
227
+ .message-bubble--user :global(.message-markdown a) {
228
+ color: var(--fd-primary-foreground);
229
+ text-decoration: underline;
230
+ opacity: 0.85;
231
+ }
232
+
233
+ .message-bubble--user :global(.message-markdown blockquote) {
234
+ border-left-color: color-mix(in srgb, var(--fd-primary-foreground) 40%, transparent);
235
+ color: var(--fd-primary-foreground);
236
+ opacity: 0.8;
237
+ }
238
+
239
+ .message-bubble__footer {
240
+ display: flex;
241
+ align-items: center;
242
+ flex-wrap: wrap;
243
+ gap: var(--fd-space-md);
244
+ margin-top: var(--fd-space-xs);
245
+ padding-top: var(--fd-space-3xs);
246
+ border-top: 1px solid var(--fd-border);
247
+ font-size: var(--fd-text-xs);
248
+ color: var(--fd-muted-foreground);
249
+ }
250
+
251
+ .message-bubble--user .message-bubble__footer {
252
+ justify-content: flex-end;
253
+ border-top-color: color-mix(in srgb, var(--fd-primary-foreground) 20%, transparent);
254
+ color: var(--fd-primary-foreground);
255
+ opacity: 0.75;
256
+ }
257
+
258
+ .message-bubble__node,
259
+ .message-bubble__duration {
260
+ display: flex;
261
+ align-items: center;
262
+ gap: var(--fd-space-3xs);
263
+ }
264
+
265
+ @media (max-width: 640px) {
266
+ .message-bubble {
267
+ padding: 2px var(--fd-space-md);
268
+ gap: var(--fd-space-xs);
269
+ }
270
+
271
+ .message-bubble__content {
272
+ max-width: calc(100% - 2.5rem);
273
+ padding: var(--fd-space-xs) var(--fd-space-sm);
274
+ }
275
+
276
+ .message-bubble__avatar {
277
+ width: 1.625rem;
278
+ height: 1.625rem;
279
+ font-size: var(--fd-text-sm);
280
+ }
281
+
282
+ .message-bubble__footer {
283
+ gap: var(--fd-space-xs);
284
+ font-size: var(--fd-text-2xs);
285
+ }
286
+ }
287
+ </style>
@@ -0,0 +1,10 @@
1
+ import type { PlaygroundMessage } from '../../types/playground.js';
2
+ interface Props {
3
+ message: PlaygroundMessage;
4
+ showTimestamp?: boolean;
5
+ isLast?: boolean;
6
+ enableMarkdown?: boolean;
7
+ }
8
+ declare const ChatBubble: import("svelte").Component<Props, {}, "">;
9
+ type ChatBubble = ReturnType<typeof ChatBubble>;
10
+ export default ChatBubble;
@@ -58,23 +58,27 @@
58
58
  let runEnabled = $state(true);
59
59
 
60
60
  let inputValue = $state('');
61
- let inputField: HTMLTextAreaElement | undefined;
61
+ let inputField: HTMLTextAreaElement | undefined = $state();
62
62
 
63
63
  // Count of enableRun messages seen so far — plain let, not $state.
64
64
  // Written with untrack to make the bookkeeping intent explicit.
65
65
  let seenEnableRunCount = 0;
66
66
 
67
67
  $effect(() => {
68
- const count = getMessages().filter(m => hasEnableRunFlag(m.metadata)).length;
68
+ const count = getMessages().filter((m) => hasEnableRunFlag(m.metadata)).length;
69
69
  if (count > seenEnableRunCount) {
70
- untrack(() => { seenEnableRunCount = count; });
70
+ untrack(() => {
71
+ seenEnableRunCount = count;
72
+ });
71
73
  runEnabled = true;
72
74
  }
73
75
  });
74
76
 
75
77
  $effect(() => {
76
78
  if (getCurrentSession()?.id) {
77
- untrack(() => { seenEnableRunCount = 0; });
79
+ untrack(() => {
80
+ seenEnableRunCount = 0;
81
+ });
78
82
  runEnabled = true;
79
83
  }
80
84
  });
@@ -87,7 +91,9 @@
87
91
  if (wasExecuting && !nowExecuting && inputField) {
88
92
  tick().then(() => inputField?.focus({ preventScroll: true }));
89
93
  }
90
- untrack(() => { wasExecuting = nowExecuting; });
94
+ untrack(() => {
95
+ wasExecuting = nowExecuting;
96
+ });
91
97
  });
92
98
 
93
99
  function handleSend(): void {
@@ -159,35 +159,37 @@
159
159
  </button>
160
160
  {#if getSessions().length > 0}
161
161
  <div class="control-panel__session-popover-divider"></div>
162
- {#each getSessions() as session (session.id)}
163
- {@const isActive = getCurrentSession()?.id === session.id}
164
- <div class="control-panel__session-popover-row">
165
- <button
166
- type="button"
167
- role="menuitem"
168
- class="control-panel__session-popover-item"
169
- class:control-panel__session-popover-item--active={isActive}
170
- onclick={() => handleSelect(session.id)}
171
- >
172
- {#if isActive}
173
- <Icon icon="mdi:check" class="control-panel__session-popover-check" />
174
- {:else}
175
- <Icon icon="mdi:message-outline" />
176
- {/if}
177
- <span>{session.name}</span>
178
- </button>
179
- <button
180
- type="button"
181
- role="menuitem"
182
- class="control-panel__session-popover-delete"
183
- onclick={(e) => handleDelete(e, session.id)}
184
- title={cp.deleteSession}
185
- aria-label={cp.deleteSession}
186
- >
187
- <Icon icon="mdi:delete-outline" />
188
- </button>
189
- </div>
190
- {/each}
162
+ <div class="control-panel__session-popover-list">
163
+ {#each getSessions() as session (session.id)}
164
+ {@const isActive = getCurrentSession()?.id === session.id}
165
+ <div class="control-panel__session-popover-row">
166
+ <button
167
+ type="button"
168
+ role="menuitem"
169
+ class="control-panel__session-popover-item"
170
+ class:control-panel__session-popover-item--active={isActive}
171
+ onclick={() => handleSelect(session.id)}
172
+ >
173
+ {#if isActive}
174
+ <Icon icon="mdi:check" class="control-panel__session-popover-check" />
175
+ {:else}
176
+ <Icon icon="mdi:message-outline" />
177
+ {/if}
178
+ <span>{session.name}</span>
179
+ </button>
180
+ <button
181
+ type="button"
182
+ role="menuitem"
183
+ class="control-panel__session-popover-delete"
184
+ onclick={(e) => handleDelete(e, session.id)}
185
+ title={cp.deleteSession}
186
+ aria-label={cp.deleteSession}
187
+ >
188
+ <Icon icon="mdi:delete-outline" />
189
+ </button>
190
+ </div>
191
+ {/each}
192
+ </div>
191
193
  {/if}
192
194
  </div>
193
195
  {/if}
@@ -333,6 +335,9 @@
333
335
  z-index: 50;
334
336
  min-width: 220px;
335
337
  max-width: 300px;
338
+ max-height: min(60vh, 420px);
339
+ display: flex;
340
+ flex-direction: column;
336
341
  padding: var(--fd-space-xs);
337
342
  background-color: var(--fd-background);
338
343
  border: 1px solid var(--fd-border);
@@ -344,6 +349,13 @@
344
349
  height: 1px;
345
350
  background-color: var(--fd-border-muted);
346
351
  margin: var(--fd-space-xs) 0;
352
+ flex-shrink: 0;
353
+ }
354
+
355
+ .control-panel__session-popover-list {
356
+ flex: 1 1 auto;
357
+ min-height: 0;
358
+ overflow-y: auto;
347
359
  }
348
360
 
349
361
  .control-panel__session-popover-row {
@@ -396,6 +408,7 @@
396
408
  color: var(--fd-primary);
397
409
  font-weight: 500;
398
410
  width: 100%;
411
+ flex: 0 0 auto;
399
412
  }
400
413
 
401
414
  .control-panel__session-popover-item--new :global(svg) {
@@ -22,6 +22,8 @@
22
22
  onInterruptResolved?: () => void;
23
23
  /** Optional callback that, when provided, shows a "New session" CTA in the welcome state */
24
24
  onCreateSession?: () => void;
25
+ /** Called when the user scrolls near the top to load older messages */
26
+ onLoadOlder?: () => void | Promise<void>;
25
27
  }
26
28
 
27
29
  let {
@@ -31,7 +33,8 @@
31
33
  allowLogs = true,
32
34
  compactSystemMessages = true,
33
35
  onInterruptResolved,
34
- onCreateSession
36
+ onCreateSession,
37
+ onLoadOlder
35
38
  }: Props = $props();
36
39
 
37
40
  const ec = $derived(m().playground.executionConsole);
@@ -50,6 +53,7 @@
50
53
  {allowLogs}
51
54
  {compactSystemMessages}
52
55
  {onInterruptResolved}
56
+ {onLoadOlder}
53
57
  welcome={welcomeState}
54
58
  emptySession={readyState}
55
59
  />
@@ -8,6 +8,8 @@ interface Props {
8
8
  onInterruptResolved?: () => void;
9
9
  /** Optional callback that, when provided, shows a "New session" CTA in the welcome state */
10
10
  onCreateSession?: () => void;
11
+ /** Called when the user scrolls near the top to load older messages */
12
+ onLoadOlder?: () => void | Promise<void>;
11
13
  }
12
14
  declare const ExecutionConsole: import("svelte").Component<Props, {}, "">;
13
15
  type ExecutionConsole = ReturnType<typeof ExecutionConsole>;
@@ -119,8 +119,13 @@
119
119
  }
120
120
 
121
121
  @keyframes pulse {
122
- 0%, 100% { opacity: 1; }
123
- 50% { opacity: 0.4; }
122
+ 0%,
123
+ 100% {
124
+ opacity: 1;
125
+ }
126
+ 50% {
127
+ opacity: 0.4;
128
+ }
124
129
  }
125
130
 
126
131
  :global(.execution-list__status-icon) {
@@ -0,0 +1,88 @@
1
+ <!--
2
+ HierarchyTrail Component
3
+
4
+ Renders a chevron-separated path of MessageHierarchyItem entries. The
5
+ path is a *display*, not a list users navigate, so the SR announcement
6
+ comes entirely from the wrapper's aria-label (built from the labels of
7
+ the items themselves) and the visible chips are hidden from AT to avoid
8
+ the path being announced twice.
9
+ -->
10
+
11
+ <script lang="ts">
12
+ import Icon from '@iconify/svelte';
13
+ import type { MessageHierarchyItem } from '../../types/playground.js';
14
+ import { m } from '../../messages/index.js';
15
+
16
+ interface Props {
17
+ items: MessageHierarchyItem[];
18
+ }
19
+
20
+ let { items }: Props = $props();
21
+
22
+ const ariaLabel = $derived.by(() => {
23
+ const path = items.map((i) => i.label).join(' / ');
24
+ return m().playground.messageAnnotations.hierarchyOf({ path });
25
+ });
26
+ </script>
27
+
28
+ {#if items.length > 0}
29
+ <span class="hierarchy-trail" aria-label={ariaLabel}>
30
+ {#each items as item (item.id)}
31
+ <span class="hierarchy-trail__item" aria-hidden="true">
32
+ {#if item.icon}
33
+ <Icon icon={item.icon} class="hierarchy-trail__icon" />
34
+ {/if}
35
+ <span class="hierarchy-trail__label">{item.label}</span>
36
+ </span>
37
+ {/each}
38
+ </span>
39
+ {/if}
40
+
41
+ <style>
42
+ .hierarchy-trail {
43
+ display: inline-flex;
44
+ flex-wrap: wrap;
45
+ align-items: center;
46
+ gap: var(--fd-space-3xs);
47
+ font-size: var(--fd-text-2xs);
48
+ color: var(--fd-muted-foreground);
49
+ min-width: 0;
50
+ }
51
+
52
+ .hierarchy-trail__item {
53
+ display: inline-flex;
54
+ align-items: center;
55
+ gap: var(--fd-space-3xs);
56
+ min-width: 0;
57
+ }
58
+
59
+ /* Chevron separator between items, drawn as a pseudo-element so it
60
+ doesn't enter the markup (and so it never appears to AT — the items
61
+ are already aria-hidden, but belt-and-braces). U+203A SINGLE
62
+ RIGHT-POINTING ANGLE QUOTATION MARK matches mdi:chevron-right
63
+ closely enough without a second iconify dependency. */
64
+ .hierarchy-trail__item:not(:last-child)::after {
65
+ content: '\203A';
66
+ margin-inline-start: var(--fd-space-3xs);
67
+ opacity: 0.5;
68
+ }
69
+
70
+ .hierarchy-trail__label {
71
+ overflow: hidden;
72
+ text-overflow: ellipsis;
73
+ white-space: nowrap;
74
+ max-width: 8rem;
75
+ }
76
+
77
+ @media (max-width: 640px) {
78
+ .hierarchy-trail__label {
79
+ max-width: 5rem;
80
+ }
81
+ }
82
+
83
+ .hierarchy-trail :global(.hierarchy-trail__icon) {
84
+ flex-shrink: 0;
85
+ font-size: 0.875em;
86
+ opacity: 0.7;
87
+ }
88
+ </style>
@@ -0,0 +1,7 @@
1
+ import type { MessageHierarchyItem } from '../../types/playground.js';
2
+ interface Props {
3
+ items: MessageHierarchyItem[];
4
+ }
5
+ declare const HierarchyTrail: import("svelte").Component<Props, {}, "">;
6
+ type HierarchyTrail = ReturnType<typeof HierarchyTrail>;
7
+ export default HierarchyTrail;