@flowdrop/flowdrop 1.3.0 → 1.5.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 (114) hide show
  1. package/README.md +68 -24
  2. package/dist/adapters/WorkflowAdapter.js +2 -22
  3. package/dist/adapters/agentspec/autoLayout.d.ts +51 -5
  4. package/dist/adapters/agentspec/autoLayout.js +120 -23
  5. package/dist/chat/commandClassifier.d.ts +19 -0
  6. package/dist/chat/commandClassifier.js +30 -0
  7. package/dist/chat/index.d.ts +27 -0
  8. package/dist/chat/index.js +32 -0
  9. package/dist/chat/responseParser.d.ts +21 -0
  10. package/dist/chat/responseParser.js +87 -0
  11. package/dist/commands/batch.d.ts +18 -0
  12. package/dist/commands/batch.js +56 -0
  13. package/dist/commands/executor.d.ts +37 -0
  14. package/dist/commands/executor.js +1044 -0
  15. package/dist/commands/index.d.ts +14 -0
  16. package/dist/commands/index.js +17 -0
  17. package/dist/commands/parser.d.ts +16 -0
  18. package/dist/commands/parser.js +278 -0
  19. package/dist/commands/positioner.d.ts +19 -0
  20. package/dist/commands/positioner.js +33 -0
  21. package/dist/commands/storeIntegration.svelte.d.ts +16 -0
  22. package/dist/commands/storeIntegration.svelte.js +67 -0
  23. package/dist/commands/types.d.ts +343 -0
  24. package/dist/commands/types.js +45 -0
  25. package/dist/components/App.svelte +431 -17
  26. package/dist/components/App.svelte.d.ts +10 -0
  27. package/dist/components/CanvasBanner.stories.svelte +6 -2
  28. package/dist/components/CanvasController.svelte +38 -0
  29. package/dist/components/CanvasController.svelte.d.ts +32 -0
  30. package/dist/components/ConfigMappingRow.svelte +130 -0
  31. package/dist/components/ConfigMappingRow.svelte.d.ts +8 -0
  32. package/dist/components/ConfigPanel.svelte +56 -7
  33. package/dist/components/ConfigPanel.svelte.d.ts +2 -0
  34. package/dist/components/FlowDropEdge.svelte +8 -57
  35. package/dist/components/Logo.svelte +14 -14
  36. package/dist/components/LogsSidebar.svelte +5 -5
  37. package/dist/components/Navbar.svelte +58 -10
  38. package/dist/components/Navbar.svelte.d.ts +7 -0
  39. package/dist/components/NodeSidebar.svelte +238 -362
  40. package/dist/components/NodeSwapPicker.svelte +537 -0
  41. package/dist/components/NodeSwapPicker.svelte.d.ts +16 -0
  42. package/dist/components/PortMappingRow.svelte +209 -0
  43. package/dist/components/PortMappingRow.svelte.d.ts +12 -0
  44. package/dist/components/SwapMappingEditor.svelte +550 -0
  45. package/dist/components/SwapMappingEditor.svelte.d.ts +12 -0
  46. package/dist/components/WorkflowEditor.svelte +99 -4
  47. package/dist/components/WorkflowEditor.svelte.d.ts +8 -0
  48. package/dist/components/chat/AIChatPanel.svelte +658 -0
  49. package/dist/components/chat/AIChatPanel.svelte.d.ts +13 -0
  50. package/dist/components/chat/CommandPreview.svelte +184 -0
  51. package/dist/components/chat/CommandPreview.svelte.d.ts +9 -0
  52. package/dist/components/console/CommandConsole.stories.svelte +93 -0
  53. package/dist/components/console/CommandConsole.stories.svelte.d.ts +27 -0
  54. package/dist/components/console/CommandConsole.svelte +259 -0
  55. package/dist/components/console/CommandConsole.svelte.d.ts +11 -0
  56. package/dist/components/console/ConsoleAutocomplete.svelte +139 -0
  57. package/dist/components/console/ConsoleAutocomplete.svelte.d.ts +21 -0
  58. package/dist/components/console/ConsoleInput.svelte +712 -0
  59. package/dist/components/console/ConsoleInput.svelte.d.ts +16 -0
  60. package/dist/components/console/ConsoleOutput.svelte +121 -0
  61. package/dist/components/console/ConsoleOutput.svelte.d.ts +11 -0
  62. package/dist/components/console/formatters.d.ts +26 -0
  63. package/dist/components/console/formatters.js +118 -0
  64. package/dist/components/interrupt/index.d.ts +1 -0
  65. package/dist/components/interrupt/index.js +1 -0
  66. package/dist/components/nodes/SimpleNode.stories.svelte +64 -0
  67. package/dist/components/nodes/SimpleNode.svelte +27 -11
  68. package/dist/components/nodes/SquareNode.stories.svelte +45 -0
  69. package/dist/components/nodes/SquareNode.svelte +27 -11
  70. package/dist/components/nodes/WorkflowNode.stories.svelte +63 -0
  71. package/dist/config/endpoints.d.ts +8 -0
  72. package/dist/config/endpoints.js +5 -0
  73. package/dist/core/index.d.ts +5 -0
  74. package/dist/core/index.js +9 -0
  75. package/dist/editor/index.d.ts +3 -1
  76. package/dist/editor/index.js +4 -2
  77. package/dist/helpers/proximityConnect.js +8 -1
  78. package/dist/helpers/workflowEditorHelper.d.ts +3 -53
  79. package/dist/helpers/workflowEditorHelper.js +13 -228
  80. package/dist/playground/index.d.ts +1 -1
  81. package/dist/playground/index.js +1 -1
  82. package/dist/schemas/v1/workflow.schema.json +107 -22
  83. package/dist/services/chatService.d.ts +65 -0
  84. package/dist/services/chatService.js +131 -0
  85. package/dist/services/historyService.d.ts +6 -4
  86. package/dist/services/historyService.js +21 -6
  87. package/dist/skins/slate.js +16 -0
  88. package/dist/stores/interruptStore.svelte.js +6 -1
  89. package/dist/stores/playgroundStore.svelte.d.ts +1 -1
  90. package/dist/stores/playgroundStore.svelte.js +11 -2
  91. package/dist/stores/portCoordinateStore.svelte.d.ts +4 -0
  92. package/dist/stores/portCoordinateStore.svelte.js +20 -26
  93. package/dist/stores/workflowStore.svelte.d.ts +31 -2
  94. package/dist/stores/workflowStore.svelte.js +84 -64
  95. package/dist/stories/EdgeDecorator.svelte +4 -4
  96. package/dist/styles/base.css +48 -0
  97. package/dist/svelte-app.d.ts +7 -1
  98. package/dist/svelte-app.js +4 -1
  99. package/dist/types/chat.d.ts +63 -0
  100. package/dist/types/chat.js +9 -0
  101. package/dist/types/events.d.ts +28 -2
  102. package/dist/types/events.js +1 -0
  103. package/dist/types/index.d.ts +8 -0
  104. package/dist/types/settings.d.ts +6 -0
  105. package/dist/types/settings.js +3 -0
  106. package/dist/utils/edgeStyling.d.ts +42 -0
  107. package/dist/utils/edgeStyling.js +176 -0
  108. package/dist/utils/nodeIds.d.ts +31 -0
  109. package/dist/utils/nodeIds.js +42 -0
  110. package/dist/utils/nodeSwap.d.ts +221 -0
  111. package/dist/utils/nodeSwap.js +686 -0
  112. package/package.json +6 -1
  113. package/dist/helpers/nodeLayoutHelper.d.ts +0 -14
  114. package/dist/helpers/nodeLayoutHelper.js +0 -19
@@ -15,11 +15,8 @@
15
15
  import { getNodeIcon, getCategoryIcon } from "../utils/icons.js";
16
16
  import { getCategoryColorToken } from "../utils/colors.js";
17
17
  import { getCategoryLabel } from "../stores/categoriesStore.svelte.js";
18
- import { SvelteSet } from "svelte/reactivity";
19
- import {
20
- getUiSettings,
21
- updateSettings,
22
- } from "../stores/settingsStore.svelte.js";
18
+ import { getUiSettings } from "../stores/settingsStore.svelte.js";
19
+ import { extractConfigDefaults } from "../utils/nodeIds.js";
23
20
 
24
21
  interface Props {
25
22
  nodes: NodeMetadata[];
@@ -36,16 +33,6 @@
36
33
  // svelte-ignore state_referenced_locally — initial default, user selects interactively
37
34
  let selectedCategory = $state(props.selectedCategory || "all");
38
35
 
39
- /**
40
- * Toggle the sidebar collapsed state
41
- * Persists the new state to settings
42
- */
43
- function toggleSidebar(): void {
44
- updateSettings({
45
- ui: { sidebarCollapsed: !getUiSettings().sidebarCollapsed },
46
- });
47
- }
48
-
49
36
  /**
50
37
  * Check if a node is compatible with the active workflow format.
51
38
  * Nodes without formats are universal (compatible with all formats).
@@ -67,6 +54,17 @@
67
54
  let filteredNodes = $derived(getFilteredNodes());
68
55
  let categories = $derived(getCategories());
69
56
 
57
+ /** Group already-filtered results by category in a single pass */
58
+ let filteredNodesByCategory = $derived.by(() => {
59
+ const map = new Map<string, NodeMetadata[]>();
60
+ for (const node of filteredNodes) {
61
+ let list = map.get(node.category);
62
+ if (!list) { list = []; map.set(node.category, list); }
63
+ list.push(node);
64
+ }
65
+ return map;
66
+ });
67
+
70
68
  /**
71
69
  * Get all unique categories from node types, preserving API order
72
70
  * Categories appear in the order their first node appears in the API response
@@ -74,7 +72,7 @@
74
72
  function getCategories(): NodeCategory[] {
75
73
  if (formatCompatibleNodes.length === 0) return [];
76
74
  // Use a Set to track uniqueness while preserving insertion order
77
- const seen = new SvelteSet<NodeCategory>();
75
+ const seen = new Set<NodeCategory>();
78
76
  const orderedCategories: NodeCategory[] = [];
79
77
  for (const node of formatCompatibleNodes) {
80
78
  if (!seen.has(node.category)) {
@@ -119,31 +117,13 @@
119
117
  function handleNodeDragStart(event: DragEvent, nodeType: NodeMetadata): void {
120
118
  if (!event.dataTransfer) return;
121
119
 
122
- // Extract initial config from configSchema with proper null checks
123
- let initialConfig: Record<string, unknown> = {};
124
- if (
125
- nodeType.configSchema &&
126
- typeof nodeType.configSchema === "object" &&
127
- nodeType.configSchema.properties &&
128
- typeof nodeType.configSchema.properties === "object"
129
- ) {
130
- // JSON Schema format - extract defaults
131
- Object.entries(nodeType.configSchema.properties).forEach(
132
- ([key, prop]) => {
133
- if (prop && typeof prop === "object" && "default" in prop) {
134
- initialConfig[key] = prop.default;
135
- }
136
- },
137
- );
138
- }
139
-
140
120
  // Create a new node instance from the node type
141
121
  const newNodeData = {
142
122
  type: "node",
143
123
  nodeType: nodeType.id,
144
124
  nodeData: {
145
125
  label: nodeType.name,
146
- config: initialConfig,
126
+ config: extractConfigDefaults(nodeType.configSchema),
147
127
  metadata: nodeType,
148
128
  },
149
129
  };
@@ -188,93 +168,83 @@
188
168
  return getCategoryLabel(category);
189
169
  }
190
170
 
191
- /**
192
- * Get node types for category
193
- * Preserves the API order - no client-side sorting applied
194
- */
195
- function getNodesForCategory(category: NodeCategory): NodeMetadata[] {
196
- return formatCompatibleNodes.filter((node) => node.category === category);
197
- }
198
-
199
- /**
200
- * Get filtered nodes for category
201
- */
202
- function getFilteredNodesForCategory(category: NodeCategory): NodeMetadata[] {
203
- let nodes = getNodesForCategory(category);
204
-
205
- // Filter by search query
206
- if (searchInput.trim()) {
207
- const query = searchInput.toLowerCase();
208
- nodes = nodes.filter(
209
- (node) =>
210
- node.name.toLowerCase().includes(query) ||
211
- node.description.toLowerCase().includes(query) ||
212
- node.tags?.some((tag) => tag.toLowerCase().includes(query)),
213
- );
214
- }
215
-
216
- return nodes;
217
- }
218
171
  </script>
219
172
 
220
- <!-- Components Sidebar - Always Visible -->
173
+ <!-- Components Sidebar -->
221
174
  <aside
222
175
  class="flowdrop-sidebar flowdrop-sidebar--container"
223
176
  class:flowdrop-sidebar--collapsed={isCollapsed}
224
177
  class:flowdrop-sidebar--compact={getUiSettings().compactMode}
225
- style:width="{isCollapsed ? 48 : getUiSettings().sidebarWidth}px"
178
+ style:width="{isCollapsed ? 0 : getUiSettings().sidebarWidth}px"
226
179
  aria-label="Components sidebar"
227
180
  >
228
- <!-- Header — visibility controlled by --fd-sidebar-header-display -->
229
- <div class="flowdrop-sidebar__header">
230
- <button
231
- class="flowdrop-sidebar__toggle"
232
- onclick={toggleSidebar}
233
- aria-label={isCollapsed ? "Expand sidebar" : "Collapse sidebar"}
234
- title={isCollapsed ? "Expand sidebar" : "Collapse sidebar"}
235
- >
236
- <Icon icon={isCollapsed ? "mdi:chevron-right" : "mdi:chevron-left"} />
237
- </button>
238
- {#if !isCollapsed}
239
- <div class="flowdrop-sidebar__title">
240
- <h2 class="flowdrop-text--lg flowdrop-font--bold">Components</h2>
181
+ <!-- Search Section — visibility controlled by --fd-sidebar-search-display -->
182
+ <div class="flowdrop-sidebar__search">
183
+ <div class="flowdrop-join flowdrop-w--full">
184
+ <div class="flowdrop-join__item flowdrop-flex--1">
185
+ <input
186
+ type="text"
187
+ placeholder="Search components..."
188
+ class="flowdrop-input flowdrop-join__item flowdrop-w--full"
189
+ bind:value={searchInput}
190
+ oninput={handleSearchChange}
191
+ />
241
192
  </div>
242
- {/if}
193
+ <button
194
+ class="flowdrop-btn flowdrop-join__item"
195
+ aria-label="Search components"
196
+ >
197
+ <Icon icon="mdi:magnify" class="flowdrop-icon" />
198
+ </button>
199
+ </div>
243
200
  </div>
244
201
 
245
- {#if !isCollapsed}
246
- <!-- Search Section — visibility controlled by --fd-sidebar-search-display -->
247
- <div class="flowdrop-sidebar__search">
248
- <div class="flowdrop-join flowdrop-w--full">
249
- <div class="flowdrop-join__item flowdrop-flex--1">
250
- <input
251
- type="text"
252
- placeholder="Search components..."
253
- class="flowdrop-input flowdrop-join__item flowdrop-w--full"
254
- bind:value={searchInput}
255
- oninput={handleSearchChange}
256
- />
202
+ <!-- Node Types List -->
203
+ <div class="flowdrop-sidebar__content">
204
+ {#if props.nodes?.length === 0}
205
+ <!-- No node types available -->
206
+ <div class="flowdrop-hero">
207
+ <div class="flowdrop-hero__content">
208
+ {#if props.loading}
209
+ <div class="flowdrop-mb--4">
210
+ <LoadingSpinner size="md" text="Loading from server..." />
211
+ </div>
212
+ {:else}
213
+ <div class="flowdrop-hero__icon">
214
+ <svg
215
+ xmlns="http://www.w3.org/2000/svg"
216
+ width="1em"
217
+ height="1em"
218
+ viewBox="0 0 24 24"
219
+ fill="none"
220
+ stroke="currentColor"
221
+ stroke-width="1.5"
222
+ stroke-linecap="round"
223
+ stroke-linejoin="round"
224
+ >
225
+ <path
226
+ d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"
227
+ />
228
+ <polyline points="3.27 6.96 12 12.01 20.73 6.96" />
229
+ <line x1="12" y1="22.08" x2="12" y2="12" />
230
+ </svg>
231
+ </div>
232
+ <h3 class="flowdrop-hero__title">No node types available</h3>
233
+ <p class="flowdrop-hero__description">
234
+ Node type definitions will appear here
235
+ </p>
236
+ {/if}
257
237
  </div>
258
- <button
259
- class="flowdrop-btn flowdrop-join__item"
260
- aria-label="Search components"
261
- >
262
- <Icon icon="mdi:magnify" class="flowdrop-icon" />
263
- </button>
264
238
  </div>
265
- </div>
266
-
267
- <!-- Node Types List -->
268
- <div class="flowdrop-sidebar__content">
269
- {#if props.nodes?.length === 0}
270
- <!-- No node types available -->
271
- <div class="flowdrop-hero">
272
- <div class="flowdrop-hero__content">
273
- {#if props.loading}
274
- <div class="flowdrop-mb--4">
275
- <LoadingSpinner size="md" text="Loading from server..." />
276
- </div>
277
- {:else}
239
+ {:else if searchInput.trim()}
240
+ <!-- Search Results -->
241
+ <div class="flowdrop-p--4">
242
+ <div class="flowdrop-divider">
243
+ <h3 class="flowdrop-divider__text">Search Results</h3>
244
+ </div>
245
+ {#if filteredNodes.length === 0}
246
+ <div class="flowdrop-hero">
247
+ <div class="flowdrop-hero__content">
278
248
  <div class="flowdrop-hero__icon">
279
249
  <svg
280
250
  xmlns="http://www.w3.org/2000/svg"
@@ -287,117 +257,131 @@
287
257
  stroke-linecap="round"
288
258
  stroke-linejoin="round"
289
259
  >
290
- <path
291
- d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"
292
- />
293
- <polyline points="3.27 6.96 12 12.01 20.73 6.96" />
294
- <line x1="12" y1="22.08" x2="12" y2="12" />
260
+ <circle cx="11" cy="11" r="8" />
261
+ <line x1="21" y1="21" x2="16.65" y2="16.65" />
295
262
  </svg>
296
263
  </div>
297
- <h3 class="flowdrop-hero__title">No node types available</h3>
264
+ <h3 class="flowdrop-hero__title">No components found</h3>
298
265
  <p class="flowdrop-hero__description">
299
- Node type definitions will appear here
266
+ Try adjusting your search
300
267
  </p>
301
- {/if}
302
- </div>
303
- </div>
304
- {:else if searchInput.trim()}
305
- <!-- Search Results -->
306
- <div class="flowdrop-p--4">
307
- <div class="flowdrop-divider">
308
- <h3 class="flowdrop-divider__text">Search Results</h3>
268
+ {#if props.loading}
269
+ <div class="flowdrop-mb--4">
270
+ <LoadingSpinner size="sm" text="Loading components..." />
271
+ </div>
272
+ {/if}
273
+ </div>
309
274
  </div>
310
- {#if filteredNodes.length === 0}
311
- <div class="flowdrop-hero">
312
- <div class="flowdrop-hero__content">
313
- <div class="flowdrop-hero__icon">
314
- <svg
315
- xmlns="http://www.w3.org/2000/svg"
316
- width="1em"
317
- height="1em"
318
- viewBox="0 0 24 24"
319
- fill="none"
320
- stroke="currentColor"
321
- stroke-width="1.5"
322
- stroke-linecap="round"
323
- stroke-linejoin="round"
275
+ {:else}
276
+ <div class="flowdrop-node-list">
277
+ {#each filteredNodes as nodeType (nodeType.id)}
278
+ <div
279
+ class="flowdrop-card flowdrop-card--compact flowdrop-node-item"
280
+ draggable="true"
281
+ ondragstart={(e) => handleNodeDragStart(e, nodeType)}
282
+ role="button"
283
+ tabindex="0"
284
+ >
285
+ <div class="flowdrop-card__body flowdrop-p--1 flowdrop-py--1">
286
+ <div
287
+ class="flowdrop-flex flowdrop-gap--2 flowdrop-items--center"
324
288
  >
325
- <circle cx="11" cy="11" r="8" />
326
- <line x1="21" y1="21" x2="16.65" y2="16.65" />
327
- </svg>
328
- </div>
329
- <h3 class="flowdrop-hero__title">No components found</h3>
330
- <p class="flowdrop-hero__description">
331
- Try adjusting your search
332
- </p>
333
- {#if props.loading}
334
- <div class="flowdrop-mb--4">
335
- <LoadingSpinner size="sm" text="Loading components..." />
289
+ <!-- Node Type Icon with Squircle Background -->
290
+ <span
291
+ class="flowdrop-node-icon"
292
+ style="--_icon-color: {getCategoryColorToken(
293
+ nodeType.category,
294
+ )}"
295
+ >
296
+ <Icon
297
+ icon={getNodeIcon(nodeType.icon, nodeType.category)}
298
+ />
299
+ </span>
300
+
301
+ <!-- Node Type Info - Icon and Title only -->
302
+ <h4
303
+ class="flowdrop-text--sm flowdrop-font--medium flowdrop-truncate flowdrop-flex--1"
304
+ >
305
+ {nodeType.name}
306
+ </h4>
336
307
  </div>
337
- {/if}
308
+ <p
309
+ class="flowdrop-text--xs flowdrop-text--gray flowdrop-truncate flowdrop-mt--1 flowdrop-ml--0"
310
+ >
311
+ {nodeType.description}
312
+ </p>
313
+ </div>
338
314
  </div>
339
- </div>
340
- {:else}
341
- <div class="flowdrop-node-list">
342
- {#each filteredNodes as nodeType (nodeType.id)}
343
- <div
344
- class="flowdrop-card flowdrop-card--compact flowdrop-node-item"
345
- draggable="true"
346
- ondragstart={(e) => handleNodeDragStart(e, nodeType)}
347
- role="button"
348
- tabindex="0"
349
- >
350
- <div class="flowdrop-card__body flowdrop-p--1 flowdrop-py--1">
315
+ {/each}
316
+ </div>
317
+ {/if}
318
+ </div>
319
+ {:else}
320
+ <!-- Show categories with details when no search is active -->
321
+ <div class="flowdrop-p--4">
322
+ <!-- Category-specific details -->
323
+ <div class="flowdrop-category-list">
324
+ {#each categories as category (category)}
325
+ {@const categoryNodes = filteredNodesByCategory.get(category) ?? []}
326
+ {#if categoryNodes.length > 0}
327
+ <!-- Flat style: label + dot+name rows (shown/hidden via CSS token) -->
328
+ <div class="fd-sidebar-flat-section">
329
+ <div class="fd-sidebar-flat-category">
330
+ {getCategoryDisplayName(category).toUpperCase()}
331
+ </div>
332
+ <div class="fd-sidebar-flat-list">
333
+ {#each categoryNodes as nodeType (nodeType.id)}
351
334
  <div
352
- class="flowdrop-flex flowdrop-gap--2 flowdrop-items--center"
335
+ class="fd-sidebar-flat-item"
336
+ draggable="true"
337
+ ondragstart={(e) => handleNodeDragStart(e, nodeType)}
338
+ onclick={() => handleNodeClick(nodeType)}
339
+ role="button"
340
+ tabindex="0"
341
+ onkeydown={(e) => {
342
+ if (e.key === "Enter" || e.key === " ") {
343
+ e.preventDefault();
344
+ handleNodeClick(nodeType);
345
+ }
346
+ }}
353
347
  >
354
- <!-- Node Type Icon with Squircle Background -->
355
348
  <span
356
- class="flowdrop-node-icon"
357
- style="--_icon-color: {getCategoryColorToken(
349
+ class="fd-sidebar-flat-dot"
350
+ style="background: {getCategoryColorToken(
358
351
  nodeType.category,
359
352
  )}"
360
- >
361
- <Icon
362
- icon={getNodeIcon(nodeType.icon, nodeType.category)}
363
- />
364
- </span>
365
-
366
- <!-- Node Type Info - Icon and Title only -->
367
- <h4
368
- class="flowdrop-text--sm flowdrop-font--medium flowdrop-truncate flowdrop-flex--1"
369
- >
370
- {nodeType.name}
371
- </h4>
353
+ ></span>
354
+ <span class="fd-sidebar-flat-name">{nodeType.name}</span>
372
355
  </div>
373
- <p
374
- class="flowdrop-text--xs flowdrop-text--gray flowdrop-truncate flowdrop-mt--1 flowdrop-ml--0"
356
+ {/each}
357
+ </div>
358
+ </div>
359
+ <!-- Card style: <details> accordion (shown/hidden via CSS token) -->
360
+ <details
361
+ class="flowdrop-details fd-sidebar-card-section"
362
+ open={props.categoriesDefaultOpen || undefined}
363
+ >
364
+ <summary class="flowdrop-details__summary">
365
+ <div
366
+ class="flowdrop-flex flowdrop-gap--2 flowdrop-items--center"
367
+ >
368
+ <span
369
+ class="flowdrop-node-icon"
370
+ style="--_icon-color: {getCategoryColorToken(category)}"
375
371
  >
376
- {nodeType.description}
377
- </p>
372
+ <Icon icon={getCategoryIcon(category)} />
373
+ </span>
374
+ <span>{getCategoryDisplayName(category)}</span>
378
375
  </div>
379
- </div>
380
- {/each}
381
- </div>
382
- {/if}
383
- </div>
384
- {:else}
385
- <!-- Show categories with details when no search is active -->
386
- <div class="flowdrop-p--4">
387
- <!-- Category-specific details -->
388
- <div class="flowdrop-category-list">
389
- {#each categories as category (category)}
390
- {@const categoryNodes = getFilteredNodesForCategory(category)}
391
- {#if categoryNodes.length > 0}
392
- <!-- Flat style: label + dot+name rows (shown/hidden via CSS token) -->
393
- <div class="fd-sidebar-flat-section">
394
- <div class="fd-sidebar-flat-category">
395
- {getCategoryDisplayName(category).toUpperCase()}
376
+ <div class="flowdrop-badge flowdrop-badge--secondary">
377
+ {categoryNodes.length}
396
378
  </div>
397
- <div class="fd-sidebar-flat-list">
379
+ </summary>
380
+ <div class="flowdrop-details__content">
381
+ <div class="flowdrop-node-list">
398
382
  {#each categoryNodes as nodeType (nodeType.id)}
399
383
  <div
400
- class="fd-sidebar-flat-item"
384
+ class="flowdrop-card flowdrop-card--compact flowdrop-node-item"
401
385
  draggable="true"
402
386
  ondragstart={(e) => handleNodeDragStart(e, nodeType)}
403
387
  onclick={() => handleNodeClick(nodeType)}
@@ -410,122 +394,71 @@
410
394
  }
411
395
  }}
412
396
  >
413
- <span
414
- class="fd-sidebar-flat-dot"
415
- style="background: {getCategoryColorToken(
416
- nodeType.category,
417
- )}"
418
- ></span>
419
- <span class="fd-sidebar-flat-name">{nodeType.name}</span
420
- >
421
- </div>
422
- {/each}
423
- </div>
424
- </div>
425
- <!-- Card style: <details> accordion (shown/hidden via CSS token) -->
426
- <details
427
- class="flowdrop-details fd-sidebar-card-section"
428
- open={props.categoriesDefaultOpen || undefined}
429
- >
430
- <summary class="flowdrop-details__summary">
431
- <div
432
- class="flowdrop-flex flowdrop-gap--2 flowdrop-items--center"
433
- >
434
- <span
435
- class="flowdrop-node-icon"
436
- style="--_icon-color: {getCategoryColorToken(category)}"
437
- >
438
- <Icon icon={getCategoryIcon(category)} />
439
- </span>
440
- <span>{getCategoryDisplayName(category)}</span>
441
- </div>
442
- <div class="flowdrop-badge flowdrop-badge--secondary">
443
- {categoryNodes.length}
444
- </div>
445
- </summary>
446
- <div class="flowdrop-details__content">
447
- <div class="flowdrop-node-list">
448
- {#each categoryNodes as nodeType (nodeType.id)}
449
397
  <div
450
- class="flowdrop-card flowdrop-card--compact flowdrop-node-item"
451
- draggable="true"
452
- ondragstart={(e) => handleNodeDragStart(e, nodeType)}
453
- onclick={() => handleNodeClick(nodeType)}
454
- role="button"
455
- tabindex="0"
456
- onkeydown={(e) => {
457
- if (e.key === "Enter" || e.key === " ") {
458
- e.preventDefault();
459
- handleNodeClick(nodeType);
460
- }
461
- }}
398
+ class="flowdrop-card__body flowdrop-p--1 flowdrop-py--1"
462
399
  >
463
400
  <div
464
- class="flowdrop-card__body flowdrop-p--1 flowdrop-py--1"
401
+ class="flowdrop-flex flowdrop-gap--2 flowdrop-items--center"
465
402
  >
466
- <div
467
- class="flowdrop-flex flowdrop-gap--2 flowdrop-items--center"
403
+ <!-- Node Type Icon with Squircle Background -->
404
+ <span
405
+ class="flowdrop-node-icon"
406
+ style="--_icon-color: {getCategoryColorToken(
407
+ nodeType.category,
408
+ )}"
468
409
  >
469
- <!-- Node Type Icon with Squircle Background -->
470
- <span
471
- class="flowdrop-node-icon"
472
- style="--_icon-color: {getCategoryColorToken(
410
+ <Icon
411
+ icon={getNodeIcon(
412
+ nodeType.icon,
473
413
  nodeType.category,
474
- )}"
475
- >
476
- <Icon
477
- icon={getNodeIcon(
478
- nodeType.icon,
479
- nodeType.category,
480
- )}
481
- />
482
- </span>
483
-
484
- <!-- Node Type Info - Icon and Title only -->
485
- <h4
486
- class="flowdrop-text--sm flowdrop-font--medium flowdrop-truncate flowdrop-flex--1"
487
- >
488
- {nodeType.name}
489
- </h4>
490
- </div>
491
- <p
492
- class="flowdrop-text--xs flowdrop-text--gray flowdrop-truncate flowdrop-mt--1 flowdrop-ml--0"
414
+ )}
415
+ />
416
+ </span>
417
+
418
+ <!-- Node Type Info - Icon and Title only -->
419
+ <h4
420
+ class="flowdrop-text--sm flowdrop-font--medium flowdrop-truncate flowdrop-flex--1"
493
421
  >
494
- {nodeType.description}
495
- </p>
422
+ {nodeType.name}
423
+ </h4>
496
424
  </div>
425
+ <p
426
+ class="flowdrop-text--xs flowdrop-text--gray flowdrop-truncate flowdrop-mt--1 flowdrop-ml--0"
427
+ >
428
+ {nodeType.description}
429
+ </p>
497
430
  </div>
498
- {/each}
499
- </div>
431
+ </div>
432
+ {/each}
500
433
  </div>
501
- </details>
502
- {/if}
503
- {/each}
504
- </div>
434
+ </div>
435
+ </details>
436
+ {/if}
437
+ {/each}
505
438
  </div>
506
- {/if}
507
- </div>
439
+ </div>
440
+ {/if}
441
+ </div>
508
442
 
509
- <!-- Footer -->
510
- <div class="flowdrop-sidebar__footer">
443
+ <!-- Footer -->
444
+ <div class="flowdrop-sidebar__footer">
445
+ <div class="flowdrop-flex flowdrop-gap--4">
511
446
  <div class="flowdrop-flex flowdrop-gap--4">
512
- <div class="flowdrop-flex flowdrop-gap--4">
513
- {#if props.loading && props.nodes?.length === 0}
514
- <span class="flowdrop-text--xs flowdrop-text--gray"
515
- >Loading components...</span
516
- >
517
- {:else}
518
- <span class="flowdrop-text--xs flowdrop-text--gray"
519
- >Total: {props.nodes?.length || 0} components</span
520
- >
521
- <span class="flowdrop-text--xs flowdrop-text--gray"
522
- >Showing: {filteredNodes.length}</span
523
- >
524
- {/if}
525
- </div>
447
+ {#if props.loading && props.nodes?.length === 0}
448
+ <span class="flowdrop-text--xs flowdrop-text--gray"
449
+ >Loading components...</span
450
+ >
451
+ {:else}
452
+ <span class="flowdrop-text--xs flowdrop-text--gray"
453
+ >Total: {props.nodes?.length || 0} components</span
454
+ >
455
+ <span class="flowdrop-text--xs flowdrop-text--gray"
456
+ >Showing: {filteredNodes.length}</span
457
+ >
458
+ {/if}
526
459
  </div>
527
460
  </div>
528
- {/if}
461
+ </div>
529
462
  </aside>
530
463
 
531
464
  <style>
@@ -549,21 +482,14 @@
549
482
  height: 100%;
550
483
  }
551
484
 
552
- /* Collapsed state */
485
+ /* Collapsed state — fully hidden */
553
486
  .flowdrop-sidebar--collapsed {
554
487
  overflow: hidden;
555
- }
556
-
557
- .flowdrop-sidebar--collapsed .flowdrop-sidebar__header {
558
- justify-content: center;
559
- padding: 0.75rem 0.5rem;
488
+ border-right: none;
489
+ box-shadow: none;
560
490
  }
561
491
 
562
492
  /* Compact mode styles */
563
- .flowdrop-sidebar--compact .flowdrop-sidebar__header {
564
- padding: 0.5rem 0.75rem;
565
- }
566
-
567
493
  .flowdrop-sidebar--compact .flowdrop-sidebar__search {
568
494
  padding: 0.5rem 0.75rem;
569
495
  }
@@ -591,56 +517,6 @@
591
517
  gap: 0.25rem;
592
518
  }
593
519
 
594
- /* Toggle button */
595
- .flowdrop-sidebar__toggle {
596
- display: flex;
597
- align-items: center;
598
- justify-content: center;
599
- width: 2rem;
600
- height: 2rem;
601
- border: none;
602
- background: transparent;
603
- color: var(--fd-muted-foreground);
604
- border-radius: var(--fd-radius-md);
605
- cursor: pointer;
606
- transition:
607
- color var(--fd-transition-fast),
608
- background-color var(--fd-transition-fast);
609
- flex-shrink: 0;
610
- }
611
-
612
- .flowdrop-sidebar__toggle:hover {
613
- color: var(--fd-foreground);
614
- background-color: var(--fd-subtle);
615
- }
616
-
617
- .flowdrop-sidebar__toggle:focus {
618
- outline: none;
619
- box-shadow: 0 0 0 2px var(--fd-ring);
620
- }
621
-
622
- .flowdrop-sidebar__header {
623
- background: var(--fd-header);
624
- border-bottom: 1px solid var(--fd-border);
625
- padding: 0.75rem 1rem;
626
- display: var(--fd-sidebar-header-display, flex);
627
- align-items: center;
628
- justify-content: space-between;
629
- flex-shrink: 0;
630
- }
631
-
632
- .flowdrop-sidebar__title {
633
- display: flex;
634
- align-items: center;
635
- }
636
-
637
- .flowdrop-sidebar__title h2 {
638
- font-size: 1rem;
639
- font-weight: 600;
640
- margin: 0;
641
- color: var(--fd-foreground);
642
- }
643
-
644
520
  .flowdrop-sidebar__search {
645
521
  padding: 0.75rem 1rem;
646
522
  background-color: var(--fd-background);