@flowdrop/flowdrop 2.0.0-beta.2 → 2.0.0-beta.4

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 (113) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/MIGRATION-2.0.md +13 -0
  3. package/README.md +5 -5
  4. package/dist/components/App.svelte +36 -191
  5. package/dist/components/App.svelte.d.ts +2 -7
  6. package/dist/components/Button.stories.svelte +65 -0
  7. package/dist/components/Button.stories.svelte.d.ts +19 -0
  8. package/dist/components/Button.svelte +62 -0
  9. package/dist/components/Button.svelte.d.ts +24 -0
  10. package/dist/components/CanvasIconButton.svelte +76 -0
  11. package/dist/components/CanvasIconButton.svelte.d.ts +18 -0
  12. package/dist/components/ConfigForm.svelte +4 -23
  13. package/dist/components/ConfigPanel.svelte +4 -3
  14. package/dist/components/EditorStatusBar.stories.svelte +44 -0
  15. package/dist/components/EditorStatusBar.stories.svelte.d.ts +27 -0
  16. package/dist/components/EditorStatusBar.svelte +99 -0
  17. package/dist/components/EditorStatusBar.svelte.d.ts +15 -0
  18. package/dist/components/IconButton.svelte +80 -0
  19. package/dist/components/IconButton.svelte.d.ts +30 -0
  20. package/dist/components/Input.svelte +74 -0
  21. package/dist/components/Input.svelte.d.ts +17 -0
  22. package/dist/components/LogoWordmark.svelte +113 -0
  23. package/dist/components/LogoWordmark.svelte.d.ts +26 -0
  24. package/dist/components/Navbar.svelte +17 -63
  25. package/dist/components/Navbar.svelte.d.ts +3 -0
  26. package/dist/components/NodeSidebar.svelte +17 -122
  27. package/dist/components/NodeSwapPicker.svelte +10 -28
  28. package/dist/components/PortMappingRow.svelte +0 -2
  29. package/dist/components/SchemaForm.svelte +0 -12
  30. package/dist/components/Select.svelte +53 -0
  31. package/dist/components/Select.svelte.d.ts +15 -0
  32. package/dist/components/SettingsModal.svelte +0 -5
  33. package/dist/components/SettingsPanel.svelte +2 -6
  34. package/dist/components/Textarea.svelte +39 -0
  35. package/dist/components/Textarea.svelte.d.ts +12 -0
  36. package/dist/components/ThemeToggle.svelte +15 -94
  37. package/dist/components/UniversalNode.svelte +32 -1
  38. package/dist/components/WorkflowEditor.svelte +62 -51
  39. package/dist/components/WorkflowEditor.svelte.d.ts +18 -0
  40. package/dist/components/chat/AIChatPanel.svelte +1 -1
  41. package/dist/components/console/ConsoleAutocomplete.svelte +1 -1
  42. package/dist/components/console/ConsoleOutput.svelte +2 -2
  43. package/dist/components/form/FormArray.svelte +37 -173
  44. package/dist/components/form/FormAutocomplete.svelte +10 -6
  45. package/dist/components/form/FormCheckboxGroup.svelte +1 -5
  46. package/dist/components/form/FormCodeEditor.svelte +9 -7
  47. package/dist/components/form/FormField.svelte +5 -44
  48. package/dist/components/form/FormFieldLight.svelte +8 -47
  49. package/dist/components/form/FormFieldset.svelte +1 -1
  50. package/dist/components/form/FormMarkdownEditor.svelte +8 -5
  51. package/dist/components/form/FormNumberField.svelte +4 -36
  52. package/dist/components/form/FormRangeField.svelte +18 -27
  53. package/dist/components/form/FormSelect.svelte +13 -75
  54. package/dist/components/form/FormTemplateEditor.svelte +6 -4
  55. package/dist/components/form/FormTextField.svelte +3 -35
  56. package/dist/components/form/FormTextarea.svelte +4 -39
  57. package/dist/components/form/FormToggle.svelte +0 -4
  58. package/dist/components/form/resolveFieldType.d.ts +24 -0
  59. package/dist/components/form/resolveFieldType.js +55 -0
  60. package/dist/components/icons/CloseIcon.svelte +6 -0
  61. package/dist/components/icons/CloseIcon.svelte.d.ts +26 -0
  62. package/dist/components/icons/CommandLineIcon.svelte +15 -0
  63. package/dist/components/icons/CommandLineIcon.svelte.d.ts +26 -0
  64. package/dist/components/icons/MenuIcon.svelte +4 -0
  65. package/dist/components/icons/MenuIcon.svelte.d.ts +26 -0
  66. package/dist/components/icons/MenuOpenIcon.svelte +6 -0
  67. package/dist/components/icons/MenuOpenIcon.svelte.d.ts +26 -0
  68. package/dist/components/interrupt/ChoicePrompt.svelte +0 -10
  69. package/dist/components/interrupt/ConfirmationPrompt.svelte +0 -5
  70. package/dist/components/interrupt/InterruptBubble.svelte +0 -10
  71. package/dist/components/interrupt/ReviewPrompt.svelte +0 -20
  72. package/dist/components/interrupt/TextInputPrompt.svelte +0 -6
  73. package/dist/components/layouts/MainLayout.svelte +4 -5
  74. package/dist/components/nodes/AtomNode.svelte +46 -34
  75. package/dist/components/nodes/GatewayNode.svelte +91 -99
  76. package/dist/components/nodes/IdeaNode.svelte +62 -90
  77. package/dist/components/nodes/NodeConfigButton.svelte +86 -0
  78. package/dist/components/nodes/NodeConfigButton.svelte.d.ts +15 -0
  79. package/dist/components/nodes/NotesNode.svelte +70 -81
  80. package/dist/components/nodes/SimpleNode.svelte +28 -78
  81. package/dist/components/nodes/SquareNode.svelte +79 -109
  82. package/dist/components/nodes/TerminalNode.svelte +28 -86
  83. package/dist/components/nodes/ToolNode.svelte +82 -95
  84. package/dist/components/nodes/WorkflowNode.svelte +91 -100
  85. package/dist/components/playground/ChatInput.svelte +0 -1
  86. package/dist/components/playground/InputCollector.svelte +11 -48
  87. package/dist/components/playground/PlaygroundApp.svelte +1 -1
  88. package/dist/components/playground/PlaygroundStudio.svelte +0 -5
  89. package/dist/messages/index.d.ts +1 -1
  90. package/dist/messages/index.js +1 -1
  91. package/dist/openapi/v1/openapi.yaml +2 -2
  92. package/dist/playground/mount.d.ts +9 -5
  93. package/dist/playground/mount.js +9 -5
  94. package/dist/skins/drafter.d.ts +30 -0
  95. package/dist/skins/drafter.js +198 -0
  96. package/dist/skins/index.d.ts +2 -1
  97. package/dist/skins/index.js +4 -2
  98. package/dist/styles/base.css +285 -14
  99. package/dist/styles/tokens.css +60 -2
  100. package/dist/svelte-app.d.ts +6 -0
  101. package/dist/svelte-app.js +71 -109
  102. package/dist/themes/drafter.d.ts +2 -0
  103. package/dist/themes/drafter.js +15 -0
  104. package/dist/themes/index.d.ts +2 -1
  105. package/dist/themes/index.js +8 -2
  106. package/dist/types/events.d.ts +18 -0
  107. package/dist/types/events.js +2 -1
  108. package/dist/types/settings.d.ts +1 -1
  109. package/dist/types/settings.js +1 -1
  110. package/dist/types/skin.d.ts +1 -1
  111. package/dist/types/theme.d.ts +16 -2
  112. package/dist/utils/connections.js +14 -50
  113. package/package.json +1 -1
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { ConfigValues, NodeMetadata } from '../../types/index.js';
3
3
  import Icon from '@iconify/svelte';
4
+ import NodeConfigButton from './NodeConfigButton.svelte';
4
5
  import MarkdownDisplay from '../MarkdownDisplay.svelte';
5
6
  import { m } from '../../messages/index.js';
6
7
 
@@ -88,28 +89,17 @@
88
89
  function handleDoubleClick(): void {
89
90
  openConfigSidebar();
90
91
  }
91
-
92
- /**
93
- * Handles keyboard events for accessibility
94
- * @param event - The keyboard event
95
- */
96
- function handleKeydown(event: KeyboardEvent): void {
97
- if (event.key === 'Enter' || event.key === ' ') {
98
- event.preventDefault();
99
- handleDoubleClick();
100
- }
101
- }
102
92
  </script>
103
93
 
94
+ <!-- Presentational: focus, keyboard and selection live on xyflow's node
95
+ wrapper (see UniversalNode). double-click is a mouse convenience. -->
96
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
104
97
  <div
105
98
  class="flowdrop-notes-node {currentType.typeClass}"
106
99
  class:flowdrop-notes-node--selected={props.selected}
107
100
  class:flowdrop-notes-node--processing={props.isProcessing}
108
101
  class:flowdrop-notes-node--has-error={props.isError}
109
102
  ondblclick={handleDoubleClick}
110
- onkeydown={handleKeydown}
111
- role="button"
112
- tabindex="0"
113
103
  >
114
104
  <!-- Display Mode -->
115
105
  <div class="flowdrop-notes-node__content">
@@ -146,35 +136,34 @@
146
136
  </div>
147
137
 
148
138
  <!-- Config button -->
149
- <button
150
- class="flowdrop-notes-node__config-btn"
151
- onclick={openConfigSidebar}
152
- title={notes.configure}
153
- >
154
- <Icon icon="mdi:cog" />
155
- </button>
139
+ <NodeConfigButton onclick={openConfigSidebar} title={notes.configure} />
156
140
  </div>
157
141
 
158
142
  <style>
159
143
  .flowdrop-notes-node {
144
+ box-sizing: border-box;
160
145
  min-width: var(--fd-notes-node-min-width);
161
146
  max-width: var(--fd-notes-node-max-width);
162
147
  width: var(--fd-notes-node-width);
163
- border-radius: var(--fd-radius-xl);
164
- border: 1.5px solid var(--fd-node-border);
165
- background: var(--fd-card);
148
+ /* Grid-aligned floor; grows in 20px steps via the chrome + body below. */
149
+ min-height: var(--fd-notes-node-min-height);
150
+ border-radius: var(--fd-node-radius);
151
+ border: var(--fd-node-border-width) solid var(--fd-note-border);
152
+ background: var(--fd-node-bg);
166
153
  backdrop-filter: var(--fd-notes-node-backdrop-filter);
167
- box-shadow: var(--fd-shadow-md);
154
+ box-shadow: var(--fd-node-shadow);
168
155
  color: var(--fd-foreground);
169
156
  transition: all var(--fd-transition-fast);
170
157
  overflow: hidden;
171
158
  z-index: 5;
172
159
  }
173
160
 
174
- /* Note type: Info (blue) - subtle background tint, neutral border */
161
+ /* Note type: Info (blue) - subtle background tint, neutral border.
162
+ Uses the info accent (not primary) so an info note never reads as a
163
+ primary/success action — primary stays reserved for interactive intent. */
175
164
  .flowdrop-notes-node--info {
176
165
  background-color: var(--fd-info-muted);
177
- --_notes-icon: var(--fd-primary);
166
+ --_notes-icon: var(--fd-info);
178
167
  }
179
168
 
180
169
  /* Note type: Warning (yellow/amber) - subtle background tint */
@@ -202,29 +191,27 @@
202
191
  }
203
192
 
204
193
  .flowdrop-notes-node:hover {
205
- box-shadow: var(--fd-shadow-lg);
206
- border-color: var(--fd-node-border-hover);
194
+ box-shadow: var(--fd-node-shadow-hover);
195
+ border-color: var(--fd-note-border-hover);
207
196
  }
208
197
 
209
198
  /* Selected state - matches other node components */
210
199
  .flowdrop-notes-node--selected {
211
200
  box-shadow:
212
201
  0 0 0 2px var(--fd-primary-muted),
213
- var(--fd-shadow-lg);
202
+ var(--fd-node-shadow-hover);
214
203
  border-color: var(--fd-primary);
215
204
  }
216
205
 
217
206
  .flowdrop-notes-node--selected:hover {
218
207
  box-shadow:
219
208
  0 0 0 2px var(--fd-primary-muted),
220
- var(--fd-shadow-lg);
209
+ var(--fd-node-shadow-hover);
221
210
  border-color: var(--fd-primary);
222
211
  }
223
212
 
224
- .flowdrop-notes-node:focus-visible {
225
- outline: 2px solid var(--fd-ring);
226
- outline-offset: 2px;
227
- }
213
+ /* Focus ring is centralized in base.css (drawn on the .svelte-flow__node
214
+ wrapper, which is the focusable element). */
228
215
 
229
216
  .flowdrop-notes-node--processing {
230
217
  opacity: 0.7;
@@ -237,7 +224,11 @@
237
224
 
238
225
  /* Display Mode Styles */
239
226
  .flowdrop-notes-node__content {
240
- padding: var(--fd-space-xl);
227
+ box-sizing: border-box;
228
+ /* px on the 20px grid; bottom padding absorbs both node borders so the
229
+ chrome (padding + 40px header + 20px gap) sums to 100px and the outer
230
+ node height stays a 20px multiple as the body grows. */
231
+ padding: 20px 20px calc(20px - var(--fd-node-border-width) * 2);
241
232
  height: 100%;
242
233
  display: flex;
243
234
  flex-direction: column;
@@ -247,14 +238,16 @@
247
238
  display: flex;
248
239
  align-items: center;
249
240
  justify-content: space-between;
250
- margin-bottom: 0.75rem;
241
+ /* one 40px grid block for the icon row + a 20px gap before the body */
242
+ min-height: 40px;
243
+ margin-bottom: 20px;
251
244
  flex-shrink: 0;
252
245
  }
253
246
 
254
247
  .flowdrop-notes-node__header-left {
255
248
  display: flex;
256
249
  align-items: center;
257
- gap: var(--fd-space-md);
250
+ gap: 12px;
258
251
  }
259
252
 
260
253
  /* Squircle icon wrapper - Apple-style rounded square background */
@@ -262,9 +255,10 @@
262
255
  display: flex;
263
256
  align-items: center;
264
257
  justify-content: center;
265
- width: 2.25rem;
266
- height: 2.25rem;
267
- border-radius: 0.5rem;
258
+ /* px (not rem) so the icon stays grid-locked regardless of root font-size */
259
+ width: 36px;
260
+ height: 36px;
261
+ border-radius: 8px;
268
262
  background: color-mix(in srgb, var(--_notes-icon) var(--fd-node-icon-bg-opacity), transparent);
269
263
  flex-shrink: 0;
270
264
  transition: all var(--fd-transition-normal);
@@ -280,8 +274,8 @@
280
274
  }
281
275
 
282
276
  .flowdrop-notes-node__icon-wrapper :global(.flowdrop-notes-node__icon) {
283
- width: 1.25rem;
284
- height: 1.25rem;
277
+ width: 20px;
278
+ height: 20px;
285
279
  color: var(--fd-node-icon);
286
280
  }
287
281
 
@@ -292,10 +286,12 @@
292
286
  }
293
287
 
294
288
  .flowdrop-notes-node__body {
295
- margin-bottom: var(--fd-space-xs);
296
289
  flex: 1;
297
290
  overflow-y: auto;
298
291
  color: var(--fd-muted-foreground);
292
+ /* 20px line rows so plain-text notes grow on the grid (rich markdown with
293
+ headings/lists may not snap exactly) */
294
+ line-height: 20px;
299
295
  }
300
296
 
301
297
  /* Markdown content inherits foreground color for better readability */
@@ -303,17 +299,36 @@
303
299
  color: var(--fd-foreground);
304
300
  }
305
301
 
302
+ /* Put markdown blocks on a 20px baseline so the note grows in clean 20px steps:
303
+ each block bottom-margins one grid row (browser em-margins would land off-grid),
304
+ and every line is a 20px row. */
305
+ .flowdrop-notes-node__body
306
+ :global(:is(h1, h2, h3, h4, h5, h6, p, ul, ol, pre, blockquote, table)) {
307
+ margin: 0 0 20px;
308
+ line-height: 20px;
309
+ }
310
+
311
+ .flowdrop-notes-node__body
312
+ :global(:is(h1, h2, h3, h4, h5, h6, p, ul, ol, pre, blockquote, table):last-child) {
313
+ margin-bottom: 0;
314
+ }
315
+
316
+ .flowdrop-notes-node__body :global(li) {
317
+ margin: 0;
318
+ line-height: 20px;
319
+ }
320
+
306
321
  .flowdrop-notes-node__processing {
307
322
  display: flex;
308
323
  align-items: center;
309
- gap: var(--fd-space-xs);
324
+ gap: 8px;
310
325
  font-size: var(--fd-text-xs);
311
326
  color: var(--fd-muted-foreground);
312
327
  }
313
328
 
314
329
  .flowdrop-notes-node__spinner {
315
- width: 0.75rem;
316
- height: 0.75rem;
330
+ width: 12px;
331
+ height: 12px;
317
332
  border: 1px solid color-mix(in srgb, var(--fd-foreground) 30%, transparent);
318
333
  border-top-color: var(--fd-foreground);
319
334
  border-radius: 50%;
@@ -323,14 +338,14 @@
323
338
  .flowdrop-notes-node__error-indicator {
324
339
  display: flex;
325
340
  align-items: center;
326
- gap: var(--fd-space-xs);
341
+ gap: 8px;
327
342
  font-size: var(--fd-text-xs);
328
343
  color: var(--fd-error);
329
344
  }
330
345
 
331
346
  :global(.flowdrop-notes-node__error-icon) {
332
- width: 0.75rem;
333
- height: 0.75rem;
347
+ width: 12px;
348
+ height: 12px;
334
349
  }
335
350
 
336
351
  @keyframes spin {
@@ -339,46 +354,20 @@
339
354
  }
340
355
  }
341
356
 
342
- .flowdrop-notes-node__config-btn {
343
- position: absolute;
344
- top: var(--fd-space-xs);
345
- right: var(--fd-space-xs);
346
- width: 1.5rem;
347
- height: 1.5rem;
348
- background-color: var(--fd-backdrop);
349
- border: 1px solid var(--fd-border);
350
- border-radius: var(--fd-radius-sm);
351
- color: var(--fd-muted-foreground);
352
- cursor: pointer;
353
- display: flex;
354
- align-items: center;
355
- justify-content: center;
356
- opacity: 0;
357
- transition: all var(--fd-transition-normal);
358
- backdrop-filter: var(--fd-backdrop-blur);
359
- z-index: 15;
360
- font-size: var(--fd-text-sm);
361
- }
362
-
363
- .flowdrop-notes-node:hover .flowdrop-notes-node__config-btn {
364
- opacity: 1;
365
- }
366
-
367
- .flowdrop-notes-node__config-btn:hover {
368
- background-color: var(--fd-muted);
369
- border-color: var(--fd-border-strong);
370
- color: var(--fd-foreground);
357
+ /* Reveal the NodeConfigButton (gear) when the node is hovered. */
358
+ .flowdrop-notes-node:hover {
359
+ --fd-config-btn-opacity: 1;
371
360
  }
372
361
 
373
362
  /* Responsive design */
374
363
  @media (max-width: 640px) {
375
364
  .flowdrop-notes-node {
376
365
  min-width: 200px;
377
- max-width: 350px;
366
+ max-width: 360px;
378
367
  }
379
368
 
380
369
  .flowdrop-notes-node__content {
381
- padding: var(--fd-space-md);
370
+ padding: 12px;
382
371
  }
383
372
  }
384
373
  </style>
@@ -23,7 +23,7 @@
23
23
  import { getDataTypeColor, getCategoryColorToken } from '../../utils/colors.js';
24
24
  import { getInstance } from '../../stores/getInstance.svelte.js';
25
25
  import { applyPortOrder, getPortTop, isPortVisible } from '../../utils/portUtils.js';
26
- import CogIcon from '../icons/CogIcon.svelte';
26
+ import NodeConfigButton from './NodeConfigButton.svelte';
27
27
  import AlertCircleIcon from '../icons/AlertCircleIcon.svelte';
28
28
 
29
29
  const props = $props<{
@@ -106,19 +106,6 @@
106
106
  openConfigSidebar();
107
107
  }
108
108
 
109
- // Handle single click - only handle selection, no config opening
110
- function handleClick(): void {
111
- // Node selection is handled by Svelte Flow
112
- }
113
-
114
- // Handle keyboard events
115
- function handleKeydown(event: KeyboardEvent): void {
116
- if (event.key === 'Enter' || event.key === ' ') {
117
- event.preventDefault();
118
- handleDoubleClick();
119
- }
120
- }
121
-
122
109
  const dynamicInputs = $derived(
123
110
  ((props.data.config?.dynamicInputs as DynamicPort[]) || []).map((port) =>
124
111
  dynamicPortToNodePort(port, 'input')
@@ -175,7 +162,7 @@
175
162
  const nodeMinHeight = $derived(
176
163
  (() => {
177
164
  const maxPorts = Math.max(visibleInputPorts.length, visibleOutputPorts.length, 1);
178
- return maxPorts <= 1 ? 80 : 20 + maxPorts * 40;
165
+ return maxPorts <= 1 ? 80 : maxPorts * 40;
179
166
  })()
180
167
  );
181
168
  </script>
@@ -197,17 +184,16 @@
197
184
  {/each}
198
185
 
199
186
  <!-- Simple Node -->
187
+ <!-- Presentational: focus, keyboard and selection live on xyflow's node
188
+ wrapper (see UniversalNode). double-click is a mouse convenience. -->
189
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
200
190
  <div
201
191
  class="flowdrop-simple-node flowdrop-simple-node--normal"
202
192
  class:flowdrop-simple-node--selected={props.selected}
203
193
  class:flowdrop-simple-node--processing={props.isProcessing}
204
194
  class:flowdrop-simple-node--error={props.isError}
205
195
  style="min-height: {nodeMinHeight}px"
206
- onclick={handleClick}
207
196
  ondblclick={handleDoubleClick}
208
- onkeydown={handleKeydown}
209
- role="button"
210
- tabindex="0"
211
197
  >
212
198
  <div class="flowdrop-simple-node__header">
213
199
  <div class="flowdrop-simple-node__header-content">
@@ -250,13 +236,7 @@
250
236
  {/if}
251
237
 
252
238
  <!-- Config button -->
253
- <button
254
- class="flowdrop-simple-node__config-btn"
255
- onclick={openConfigSidebar}
256
- title="Configure node"
257
- >
258
- <CogIcon />
259
- </button>
239
+ <NodeConfigButton onclick={openConfigSidebar} title="Configure node" />
260
240
  </div>
261
241
 
262
242
  <!-- Output Handles: 1 port centered at 40px; N ports at 20px start, 40px gap -->
@@ -278,14 +258,15 @@
278
258
  <style>
279
259
  .flowdrop-simple-node {
280
260
  position: relative;
281
- background-color: var(--fd-card);
282
- border: 1.5px solid var(--fd-node-border);
283
- border-radius: var(--fd-radius-xl);
261
+ background-color: var(--fd-node-bg);
262
+ backdrop-filter: var(--fd-node-backdrop-filter);
263
+ border: var(--fd-node-border-width) solid var(--fd-node-border);
264
+ border-radius: var(--fd-node-radius);
284
265
  display: flex;
285
266
  flex-direction: column;
286
267
  cursor: pointer;
287
268
  transition: all var(--fd-transition-fast);
288
- box-shadow: var(--fd-shadow-md);
269
+ box-shadow: var(--fd-node-shadow);
289
270
  overflow: hidden;
290
271
  z-index: 10;
291
272
  color: var(--fd-foreground);
@@ -298,28 +279,26 @@
298
279
  }
299
280
 
300
281
  .flowdrop-simple-node:hover {
301
- box-shadow: var(--fd-shadow-lg);
282
+ box-shadow: var(--fd-node-shadow-hover);
302
283
  border-color: var(--fd-node-border-hover);
303
284
  }
304
285
 
305
286
  .flowdrop-simple-node--selected {
306
287
  box-shadow:
307
288
  0 0 0 2px var(--fd-primary-muted),
308
- var(--fd-shadow-lg);
289
+ var(--fd-node-shadow-hover);
309
290
  border-color: var(--fd-primary);
310
291
  }
311
292
 
312
293
  .flowdrop-simple-node--selected:hover {
313
294
  box-shadow:
314
295
  0 0 0 2px var(--fd-primary-muted),
315
- var(--fd-shadow-lg);
296
+ var(--fd-node-shadow-hover);
316
297
  border-color: var(--fd-primary);
317
298
  }
318
299
 
319
- .flowdrop-simple-node:focus-visible {
320
- outline: 2px solid var(--fd-ring);
321
- outline-offset: 2px;
322
- }
300
+ /* Focus ring is centralized in base.css (drawn on the .svelte-flow__node
301
+ wrapper, which is the focusable element). */
323
302
 
324
303
  .flowdrop-simple-node--processing {
325
304
  opacity: 0.7;
@@ -331,8 +310,9 @@
331
310
  }
332
311
 
333
312
  .flowdrop-simple-node__header {
334
- padding: var(--fd-space-xl);
335
- background: var(--fd-header);
313
+ /* px (not rem) on the 20px grid: 10px vertical, 20px horizontal. */
314
+ padding: 10px 20px;
315
+ background: var(--fd-node-header-bg);
336
316
  flex: 1;
337
317
  }
338
318
 
@@ -347,9 +327,10 @@
347
327
  display: var(--fd-node-icon-display, flex);
348
328
  align-items: center;
349
329
  justify-content: center;
350
- width: 2.25rem;
351
- height: 2.25rem;
352
- border-radius: 0.5rem;
330
+ /* px (not rem) so the icon stays grid-locked regardless of root font-size */
331
+ width: 36px;
332
+ height: 36px;
333
+ border-radius: 8px;
353
334
  background: color-mix(in srgb, var(--_icon-color) var(--fd-node-icon-bg-opacity), transparent);
354
335
  flex-shrink: 0;
355
336
  transition: all var(--fd-transition-normal);
@@ -382,8 +363,8 @@
382
363
  }
383
364
 
384
365
  .flowdrop-simple-node__icon-wrapper :global(.flowdrop-simple-node__icon) {
385
- width: 1.25rem;
386
- height: 1.25rem;
366
+ width: 20px;
367
+ height: 20px;
387
368
  color: var(--fd-node-icon);
388
369
  }
389
370
 
@@ -414,40 +395,9 @@
414
395
  height: 12px;
415
396
  }
416
397
 
417
- .flowdrop-simple-node__config-btn :global(svg) {
418
- width: 14px;
419
- height: 14px;
420
- }
421
-
422
- .flowdrop-simple-node__config-btn {
423
- position: absolute;
424
- top: var(--fd-space-xs);
425
- right: var(--fd-space-xs);
426
- width: 1.5rem;
427
- height: 1.5rem;
428
- background-color: var(--fd-backdrop);
429
- border: 1px solid var(--fd-border);
430
- border-radius: var(--fd-radius-sm);
431
- color: var(--fd-muted-foreground);
432
- cursor: pointer;
433
- display: flex;
434
- align-items: center;
435
- justify-content: center;
436
- opacity: 0;
437
- transition: all var(--fd-transition-normal);
438
- backdrop-filter: blur(4px);
439
- z-index: 15;
440
- font-size: var(--fd-text-sm);
441
- }
442
-
443
- .flowdrop-simple-node:hover .flowdrop-simple-node__config-btn {
444
- opacity: 1;
445
- }
446
-
447
- .flowdrop-simple-node__config-btn:hover {
448
- background-color: var(--fd-muted);
449
- border-color: var(--fd-border-strong);
450
- color: var(--fd-foreground);
398
+ /* Reveal the NodeConfigButton (gear) when the node is hovered. */
399
+ .flowdrop-simple-node:hover {
400
+ --fd-config-btn-opacity: 1;
451
401
  }
452
402
 
453
403
  @keyframes spin {