@flowdrop/flowdrop 2.0.0-beta.3 → 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.
- package/CHANGELOG.md +25 -0
- package/README.md +5 -5
- package/dist/components/App.svelte +15 -146
- package/dist/components/Button.stories.svelte +65 -0
- package/dist/components/Button.stories.svelte.d.ts +19 -0
- package/dist/components/Button.svelte +62 -0
- package/dist/components/Button.svelte.d.ts +24 -0
- package/dist/components/ConfigForm.svelte +4 -4
- package/dist/components/EditorStatusBar.stories.svelte +44 -0
- package/dist/components/EditorStatusBar.stories.svelte.d.ts +27 -0
- package/dist/components/EditorStatusBar.svelte +99 -0
- package/dist/components/EditorStatusBar.svelte.d.ts +15 -0
- package/dist/components/IconButton.svelte +80 -0
- package/dist/components/IconButton.svelte.d.ts +30 -0
- package/dist/components/Input.svelte +74 -0
- package/dist/components/Input.svelte.d.ts +17 -0
- package/dist/components/Navbar.svelte +9 -4
- package/dist/components/Navbar.svelte.d.ts +3 -0
- package/dist/components/NodeSidebar.svelte +13 -111
- package/dist/components/NodeSwapPicker.svelte +10 -26
- package/dist/components/Select.svelte +53 -0
- package/dist/components/Select.svelte.d.ts +15 -0
- package/dist/components/Textarea.svelte +39 -0
- package/dist/components/Textarea.svelte.d.ts +12 -0
- package/dist/components/ThemeToggle.svelte +15 -89
- package/dist/components/form/FormArray.svelte +37 -157
- package/dist/components/form/FormCheckboxGroup.svelte +1 -1
- package/dist/components/form/FormField.svelte +5 -44
- package/dist/components/form/FormFieldLight.svelte +5 -44
- package/dist/components/form/FormFieldset.svelte +1 -1
- package/dist/components/form/FormNumberField.svelte +4 -32
- package/dist/components/form/FormRangeField.svelte +17 -7
- package/dist/components/form/FormSelect.svelte +13 -79
- package/dist/components/form/FormTextField.svelte +3 -39
- package/dist/components/form/FormTextarea.svelte +4 -43
- package/dist/components/form/resolveFieldType.d.ts +24 -0
- package/dist/components/form/resolveFieldType.js +55 -0
- package/dist/components/icons/CloseIcon.svelte +6 -0
- package/dist/components/icons/CloseIcon.svelte.d.ts +26 -0
- package/dist/components/playground/InputCollector.svelte +11 -46
- package/dist/messages/index.d.ts +1 -1
- package/dist/messages/index.js +1 -1
- package/dist/openapi/v1/openapi.yaml +2 -2
- package/dist/skins/drafter.js +41 -28
- package/dist/styles/base.css +247 -5
- package/dist/styles/tokens.css +6 -0
- package/dist/svelte-app.js +68 -107
- package/dist/utils/connections.js +14 -50
- package/package.json +1 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
IconButton — typed wrapper over the `.flowdrop-btn--icon` system (base.css).
|
|
3
|
+
|
|
4
|
+
The icon-only sibling of Button.svelte: a square button that holds a single
|
|
5
|
+
glyph (Icon, inline SVG, or character). All geometry, variants, sizes and the
|
|
6
|
+
centralized focus ring live in base.css; this component is the ergonomic,
|
|
7
|
+
type-safe entry point so callers pick `variant`/`size` instead of hand-writing
|
|
8
|
+
class strings and re-declaring widths/tints in scoped styles.
|
|
9
|
+
|
|
10
|
+
Internal for now (not exported from any public entry) so the API isn't frozen
|
|
11
|
+
before GA. Existing hand-rolled square buttons migrate onto it incrementally.
|
|
12
|
+
Canvas-overlay buttons keep their bespoke components (CanvasIconButton,
|
|
13
|
+
NodeConfigButton) — those add absolute positioning, shadows and backdrop blur
|
|
14
|
+
this primitive deliberately stays out of.
|
|
15
|
+
-->
|
|
16
|
+
|
|
17
|
+
<script lang="ts">
|
|
18
|
+
import type { Snippet } from 'svelte';
|
|
19
|
+
|
|
20
|
+
interface Props {
|
|
21
|
+
/**
|
|
22
|
+
* Visual style. `ghost` (default) is transparent until hover; `default` is a
|
|
23
|
+
* flat surface with a resting border; `primary`/`danger`/`success` are quiet
|
|
24
|
+
* semantic tints (muted fill + coloured border) that go solid on press.
|
|
25
|
+
*/
|
|
26
|
+
variant?: 'ghost' | 'default' | 'primary' | 'danger' | 'success';
|
|
27
|
+
/** Square size — `md` (32px) is the base; `sm` (28px) / `lg` (36px) add a modifier */
|
|
28
|
+
size?: 'sm' | 'md' | 'lg';
|
|
29
|
+
/** Native button type */
|
|
30
|
+
type?: 'button' | 'submit' | 'reset';
|
|
31
|
+
/** Tooltip text */
|
|
32
|
+
title?: string;
|
|
33
|
+
/** Accessible label — required, since the button is icon-only */
|
|
34
|
+
ariaLabel: string;
|
|
35
|
+
/** Toggle/pressed state — applies the active tint and sets `aria-pressed` */
|
|
36
|
+
active?: boolean;
|
|
37
|
+
/** Disabled state */
|
|
38
|
+
disabled?: boolean;
|
|
39
|
+
/** Extra classes appended to the root button */
|
|
40
|
+
class?: string;
|
|
41
|
+
/** Click handler */
|
|
42
|
+
onclick?: (event: MouseEvent) => void;
|
|
43
|
+
/** The glyph (icon, svg, or character) */
|
|
44
|
+
children: Snippet;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let {
|
|
48
|
+
variant = 'ghost',
|
|
49
|
+
size = 'md',
|
|
50
|
+
type = 'button',
|
|
51
|
+
title,
|
|
52
|
+
ariaLabel,
|
|
53
|
+
active = false,
|
|
54
|
+
disabled = false,
|
|
55
|
+
class: className = '',
|
|
56
|
+
onclick,
|
|
57
|
+
children
|
|
58
|
+
}: Props = $props();
|
|
59
|
+
|
|
60
|
+
// `ghost` reuses the shared `.flowdrop-btn--ghost`; the other variants have
|
|
61
|
+
// icon-specific tint classes so they don't collide with the solid text buttons.
|
|
62
|
+
const variantClass = $derived(
|
|
63
|
+
variant === 'ghost' ? 'flowdrop-btn--ghost' : `flowdrop-btn--icon-${variant}`
|
|
64
|
+
);
|
|
65
|
+
// 'md' is the unmodified base size; only 'sm'/'lg' need a size modifier.
|
|
66
|
+
const sizeClass = $derived(size === 'md' ? '' : `flowdrop-btn--${size}`);
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<button
|
|
70
|
+
class="flowdrop-btn flowdrop-btn--icon {variantClass} {sizeClass} {className}"
|
|
71
|
+
class:is-active={active}
|
|
72
|
+
{type}
|
|
73
|
+
{title}
|
|
74
|
+
{disabled}
|
|
75
|
+
aria-label={ariaLabel}
|
|
76
|
+
aria-pressed={active ? true : undefined}
|
|
77
|
+
{onclick}
|
|
78
|
+
>
|
|
79
|
+
{@render children()}
|
|
80
|
+
</button>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
/**
|
|
4
|
+
* Visual style. `ghost` (default) is transparent until hover; `default` is a
|
|
5
|
+
* flat surface with a resting border; `primary`/`danger`/`success` are quiet
|
|
6
|
+
* semantic tints (muted fill + coloured border) that go solid on press.
|
|
7
|
+
*/
|
|
8
|
+
variant?: 'ghost' | 'default' | 'primary' | 'danger' | 'success';
|
|
9
|
+
/** Square size — `md` (32px) is the base; `sm` (28px) / `lg` (36px) add a modifier */
|
|
10
|
+
size?: 'sm' | 'md' | 'lg';
|
|
11
|
+
/** Native button type */
|
|
12
|
+
type?: 'button' | 'submit' | 'reset';
|
|
13
|
+
/** Tooltip text */
|
|
14
|
+
title?: string;
|
|
15
|
+
/** Accessible label — required, since the button is icon-only */
|
|
16
|
+
ariaLabel: string;
|
|
17
|
+
/** Toggle/pressed state — applies the active tint and sets `aria-pressed` */
|
|
18
|
+
active?: boolean;
|
|
19
|
+
/** Disabled state */
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
/** Extra classes appended to the root button */
|
|
22
|
+
class?: string;
|
|
23
|
+
/** Click handler */
|
|
24
|
+
onclick?: (event: MouseEvent) => void;
|
|
25
|
+
/** The glyph (icon, svg, or character) */
|
|
26
|
+
children: Snippet;
|
|
27
|
+
}
|
|
28
|
+
declare const IconButton: import("svelte").Component<Props, {}, "">;
|
|
29
|
+
type IconButton = ReturnType<typeof IconButton>;
|
|
30
|
+
export default IconButton;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Input — typed wrapper over the shared `.flowdrop-input` system (base.css).
|
|
3
|
+
|
|
4
|
+
All control styling (surface, border, radius, sizes, focus ring, disabled =
|
|
5
|
+
the only muted state) lives in base.css. This component is the ergonomic,
|
|
6
|
+
type-safe entry point so callers pick `size`/`invalid`/`leading`/`trailing`
|
|
7
|
+
instead of hand-writing class strings — the single place text-like fields
|
|
8
|
+
should route through. Mirrors Button.svelte.
|
|
9
|
+
|
|
10
|
+
Native attributes (type, value, placeholder, disabled, id, aria-*, oninput…)
|
|
11
|
+
are forwarded verbatim to the underlying <input>.
|
|
12
|
+
|
|
13
|
+
Internal for now (not exported from any public entry) so the API isn't frozen
|
|
14
|
+
before GA. Existing hand-rolled inputs migrate onto it incrementally.
|
|
15
|
+
-->
|
|
16
|
+
|
|
17
|
+
<script lang="ts">
|
|
18
|
+
import type { Snippet } from 'svelte';
|
|
19
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
20
|
+
|
|
21
|
+
// Omit native numeric `size` so our design-token size (matching Button) wins.
|
|
22
|
+
interface Props extends Omit<HTMLInputAttributes, 'size'> {
|
|
23
|
+
/** Size — `md` is the base `.flowdrop-input`; `sm`/`lg` add a modifier */
|
|
24
|
+
size?: 'sm' | 'md' | 'lg';
|
|
25
|
+
/** Renders the error-border state */
|
|
26
|
+
invalid?: boolean;
|
|
27
|
+
/** Extra classes appended to the input */
|
|
28
|
+
class?: string;
|
|
29
|
+
/** Optional leading affordance (e.g. a search icon) */
|
|
30
|
+
leading?: Snippet;
|
|
31
|
+
/** Optional trailing affordance */
|
|
32
|
+
trailing?: Snippet;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let {
|
|
36
|
+
size = 'md',
|
|
37
|
+
invalid = false,
|
|
38
|
+
class: className = '',
|
|
39
|
+
leading,
|
|
40
|
+
trailing,
|
|
41
|
+
...rest
|
|
42
|
+
}: Props = $props();
|
|
43
|
+
|
|
44
|
+
const inputClass = $derived(
|
|
45
|
+
[
|
|
46
|
+
'flowdrop-input',
|
|
47
|
+
size === 'md' ? '' : `flowdrop-input--${size}`,
|
|
48
|
+
invalid ? 'flowdrop-input--invalid' : '',
|
|
49
|
+
leading ? 'flowdrop-input--has-leading' : '',
|
|
50
|
+
trailing ? 'flowdrop-input--has-trailing' : '',
|
|
51
|
+
className
|
|
52
|
+
]
|
|
53
|
+
.filter(Boolean)
|
|
54
|
+
.join(' ')
|
|
55
|
+
);
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
{#if leading || trailing}
|
|
59
|
+
<div class="flowdrop-input-wrap">
|
|
60
|
+
{#if leading}
|
|
61
|
+
<span class="flowdrop-input-wrap__icon flowdrop-input-wrap__icon--leading">
|
|
62
|
+
{@render leading()}
|
|
63
|
+
</span>
|
|
64
|
+
{/if}
|
|
65
|
+
<input class={inputClass} {...rest} />
|
|
66
|
+
{#if trailing}
|
|
67
|
+
<span class="flowdrop-input-wrap__icon flowdrop-input-wrap__icon--trailing">
|
|
68
|
+
{@render trailing()}
|
|
69
|
+
</span>
|
|
70
|
+
{/if}
|
|
71
|
+
</div>
|
|
72
|
+
{:else}
|
|
73
|
+
<input class={inputClass} {...rest} />
|
|
74
|
+
{/if}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
|
+
interface Props extends Omit<HTMLInputAttributes, 'size'> {
|
|
4
|
+
/** Size — `md` is the base `.flowdrop-input`; `sm`/`lg` add a modifier */
|
|
5
|
+
size?: 'sm' | 'md' | 'lg';
|
|
6
|
+
/** Renders the error-border state */
|
|
7
|
+
invalid?: boolean;
|
|
8
|
+
/** Extra classes appended to the input */
|
|
9
|
+
class?: string;
|
|
10
|
+
/** Optional leading affordance (e.g. a search icon) */
|
|
11
|
+
leading?: Snippet;
|
|
12
|
+
/** Optional trailing affordance */
|
|
13
|
+
trailing?: Snippet;
|
|
14
|
+
}
|
|
15
|
+
declare const Input: import("svelte").Component<Props, {}, "">;
|
|
16
|
+
type Input = ReturnType<typeof Input>;
|
|
17
|
+
export default Input;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
9
|
<script lang="ts">
|
|
10
|
+
import type { Snippet } from 'svelte';
|
|
10
11
|
import Icon from '@iconify/svelte';
|
|
11
12
|
import LogoWordmark from './LogoWordmark.svelte';
|
|
12
13
|
import SettingsModal from './SettingsModal.svelte';
|
|
@@ -37,6 +38,8 @@
|
|
|
37
38
|
showSettingsSyncButton?: boolean;
|
|
38
39
|
/** Show the reset buttons in the settings modal */
|
|
39
40
|
showSettingsResetButton?: boolean;
|
|
41
|
+
/** Custom content rendered in the trailing (right) region, before the settings gear */
|
|
42
|
+
end?: Snippet;
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
let {
|
|
@@ -47,7 +50,8 @@
|
|
|
47
50
|
showSettings = true,
|
|
48
51
|
settingsCategories,
|
|
49
52
|
showSettingsSyncButton,
|
|
50
|
-
showSettingsResetButton
|
|
53
|
+
showSettingsResetButton,
|
|
54
|
+
end
|
|
51
55
|
}: Props = $props();
|
|
52
56
|
|
|
53
57
|
// Dropdown state
|
|
@@ -271,6 +275,7 @@
|
|
|
271
275
|
</div>
|
|
272
276
|
|
|
273
277
|
<div class="flowdrop-navbar__end">
|
|
278
|
+
{@render end?.()}
|
|
274
279
|
{#if showSettings}
|
|
275
280
|
<button
|
|
276
281
|
class="flowdrop-navbar__settings-btn"
|
|
@@ -519,7 +524,7 @@
|
|
|
519
524
|
transition: all var(--fd-transition-normal);
|
|
520
525
|
font-weight: 500;
|
|
521
526
|
font-size: var(--fd-text-sm);
|
|
522
|
-
height:
|
|
527
|
+
height: var(--fd-size-btn-min);
|
|
523
528
|
box-sizing: border-box;
|
|
524
529
|
background-color: var(--fd-background);
|
|
525
530
|
color: var(--fd-foreground);
|
|
@@ -535,7 +540,7 @@
|
|
|
535
540
|
position: relative;
|
|
536
541
|
display: flex;
|
|
537
542
|
align-items: center;
|
|
538
|
-
height:
|
|
543
|
+
height: var(--fd-size-btn-min);
|
|
539
544
|
}
|
|
540
545
|
|
|
541
546
|
.flowdrop-navbar__dropdown-trigger {
|
|
@@ -543,7 +548,7 @@
|
|
|
543
548
|
align-items: center;
|
|
544
549
|
justify-content: center;
|
|
545
550
|
width: 2rem;
|
|
546
|
-
height:
|
|
551
|
+
height: var(--fd-size-btn-min);
|
|
547
552
|
border: 1px solid var(--fd-border-strong);
|
|
548
553
|
border-left: none;
|
|
549
554
|
border-radius: 0 var(--fd-radius-md) var(--fd-radius-md) 0;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
1
2
|
import type { SettingsCategory } from '../types/settings.js';
|
|
2
3
|
import type { NavbarAction } from '../types/navbar.js';
|
|
3
4
|
interface BreadcrumbItem {
|
|
@@ -22,6 +23,8 @@ interface Props {
|
|
|
22
23
|
showSettingsSyncButton?: boolean;
|
|
23
24
|
/** Show the reset buttons in the settings modal */
|
|
24
25
|
showSettingsResetButton?: boolean;
|
|
26
|
+
/** Custom content rendered in the trailing (right) region, before the settings gear */
|
|
27
|
+
end?: Snippet;
|
|
25
28
|
}
|
|
26
29
|
declare const Navbar: import("svelte").Component<Props, {}, "">;
|
|
27
30
|
type Navbar = ReturnType<typeof Navbar>;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
<script lang="ts">
|
|
8
8
|
import type { NodeMetadata, NodeCategory, WorkflowFormat } from '../types/index.js';
|
|
9
9
|
import LoadingSpinner from './LoadingSpinner.svelte';
|
|
10
|
+
import Input from './Input.svelte';
|
|
10
11
|
import Icon from '@iconify/svelte';
|
|
11
12
|
import { getNodeIcon, getCategoryIcon } from '../utils/icons.js';
|
|
12
13
|
import { getCategoryColorToken } from '../utils/colors.js';
|
|
@@ -161,19 +162,17 @@
|
|
|
161
162
|
>
|
|
162
163
|
<!-- Search Section — visibility controlled by --fd-sidebar-search-display -->
|
|
163
164
|
<div class="flowdrop-sidebar__search">
|
|
164
|
-
<
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
</button>
|
|
176
|
-
</div>
|
|
165
|
+
<Input
|
|
166
|
+
type="text"
|
|
167
|
+
placeholder={m().layout.searchComponents}
|
|
168
|
+
aria-label={m().layout.searchComponents}
|
|
169
|
+
value={searchInput}
|
|
170
|
+
oninput={(e) => (searchInput = e.currentTarget.value)}
|
|
171
|
+
>
|
|
172
|
+
{#snippet leading()}
|
|
173
|
+
<Icon icon="mdi:magnify" />
|
|
174
|
+
{/snippet}
|
|
175
|
+
</Input>
|
|
177
176
|
</div>
|
|
178
177
|
|
|
179
178
|
<!-- Node Types List -->
|
|
@@ -594,106 +593,9 @@
|
|
|
594
593
|
margin-left: 0;
|
|
595
594
|
}
|
|
596
595
|
|
|
597
|
-
/*
|
|
598
|
-
.flowdrop-input {
|
|
599
|
-
padding: 0.625rem 0.75rem;
|
|
600
|
-
border: 1px solid var(--fd-border-strong);
|
|
601
|
-
border-radius: var(--fd-radius-md);
|
|
602
|
-
font-size: var(--fd-text-sm);
|
|
603
|
-
color: var(--fd-foreground);
|
|
604
|
-
background-color: var(--fd-background);
|
|
605
|
-
transition:
|
|
606
|
-
border-color var(--fd-transition-normal),
|
|
607
|
-
box-shadow var(--fd-transition-normal);
|
|
608
|
-
width: 100%;
|
|
609
|
-
height: 2.5rem;
|
|
610
|
-
box-sizing: border-box;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
.flowdrop-input:focus {
|
|
614
|
-
border-color: var(--fd-ring);
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
.flowdrop-input::placeholder {
|
|
618
|
-
color: var(--fd-muted-foreground);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
.flowdrop-btn {
|
|
622
|
-
padding: 0.625rem 0.75rem;
|
|
623
|
-
border-radius: var(--fd-radius-md);
|
|
624
|
-
font-size: var(--fd-text-sm);
|
|
625
|
-
font-weight: 500;
|
|
626
|
-
cursor: pointer;
|
|
627
|
-
transition: all var(--fd-transition-normal);
|
|
628
|
-
border: 1px solid var(--fd-border-strong);
|
|
629
|
-
display: flex;
|
|
630
|
-
align-items: center;
|
|
631
|
-
justify-content: center;
|
|
632
|
-
gap: 0.5rem;
|
|
633
|
-
background-color: var(--fd-muted);
|
|
634
|
-
color: var(--fd-foreground);
|
|
635
|
-
height: 2.5rem;
|
|
636
|
-
min-width: 2.5rem;
|
|
637
|
-
box-sizing: border-box;
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
.flowdrop-btn:hover {
|
|
641
|
-
background-color: var(--fd-subtle);
|
|
642
|
-
border-color: var(--fd-border-strong);
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
.flowdrop-btn:active {
|
|
646
|
-
background-color: var(--fd-border);
|
|
647
|
-
border-color: var(--fd-muted-foreground);
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
.flowdrop-btn:disabled {
|
|
651
|
-
background-color: var(--fd-muted-foreground);
|
|
652
|
-
border-color: var(--fd-muted-foreground);
|
|
653
|
-
cursor: not-allowed;
|
|
654
|
-
opacity: 0.6;
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
/* Join component styles - Seamless integration */
|
|
658
|
-
.flowdrop-join {
|
|
659
|
-
display: flex;
|
|
660
|
-
width: 100%;
|
|
661
|
-
border-radius: var(--fd-radius-md);
|
|
662
|
-
overflow: hidden;
|
|
663
|
-
border: 1px solid var(--fd-border-strong);
|
|
664
|
-
background-color: var(--fd-background);
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
.flowdrop-join__item {
|
|
668
|
-
border: none;
|
|
669
|
-
border-radius: 0;
|
|
670
|
-
background-color: transparent;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
.flowdrop-join__item:first-child {
|
|
674
|
-
border-right: 1px solid var(--fd-border-strong);
|
|
675
|
-
flex: 1;
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
.flowdrop-join__item:last-child {
|
|
679
|
-
border-left: none;
|
|
680
|
-
background-color: var(--fd-muted);
|
|
681
|
-
color: var(--fd-foreground);
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
.flowdrop-join__item:last-child:hover {
|
|
685
|
-
background-color: var(--fd-subtle);
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
.flowdrop-join:focus-within {
|
|
689
|
-
border-color: var(--fd-ring);
|
|
690
|
-
}
|
|
596
|
+
/* Search field renders through the shared Input primitive (base.css). */
|
|
691
597
|
|
|
692
598
|
/* Utility classes */
|
|
693
|
-
.flowdrop-w--full {
|
|
694
|
-
width: 100%;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
599
|
.flowdrop-flex--1 {
|
|
698
600
|
flex: 1;
|
|
699
601
|
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
<script lang="ts">
|
|
9
9
|
import type { NodeMetadata, NodeCategory, WorkflowFormat, WorkflowNode } from '../types/index.js';
|
|
10
10
|
import Icon from '@iconify/svelte';
|
|
11
|
+
import Input from './Input.svelte';
|
|
11
12
|
import { getNodeIcon, getCategoryIcon } from '../utils/icons.js';
|
|
12
13
|
import { getCategoryColorToken } from '../utils/colors.js';
|
|
13
14
|
import { m } from '../messages/index.js';
|
|
@@ -115,12 +116,17 @@
|
|
|
115
116
|
|
|
116
117
|
<!-- Search (hidden in minimal via --fd-sidebar-search-display) -->
|
|
117
118
|
<div class="swap-picker__search">
|
|
118
|
-
<
|
|
119
|
+
<Input
|
|
119
120
|
type="text"
|
|
120
121
|
placeholder="Search node types..."
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
aria-label="Search node types"
|
|
123
|
+
value={searchInput}
|
|
124
|
+
oninput={(e) => (searchInput = e.currentTarget.value)}
|
|
125
|
+
>
|
|
126
|
+
{#snippet leading()}
|
|
127
|
+
<Icon icon="mdi:magnify" />
|
|
128
|
+
{/snippet}
|
|
129
|
+
</Input>
|
|
124
130
|
</div>
|
|
125
131
|
|
|
126
132
|
<!-- Node list -->
|
|
@@ -298,28 +304,6 @@
|
|
|
298
304
|
flex-shrink: 0;
|
|
299
305
|
}
|
|
300
306
|
|
|
301
|
-
.swap-picker__input {
|
|
302
|
-
width: 100%;
|
|
303
|
-
padding: 0.5rem 0.75rem;
|
|
304
|
-
border: 1px solid var(--fd-border-strong);
|
|
305
|
-
border-radius: var(--fd-radius-md);
|
|
306
|
-
font-size: var(--fd-text-sm);
|
|
307
|
-
color: var(--fd-foreground);
|
|
308
|
-
background-color: var(--fd-background);
|
|
309
|
-
box-sizing: border-box;
|
|
310
|
-
transition:
|
|
311
|
-
border-color var(--fd-transition-normal),
|
|
312
|
-
box-shadow var(--fd-transition-normal);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
.swap-picker__input:focus {
|
|
316
|
-
border-color: var(--fd-ring);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
.swap-picker__input::placeholder {
|
|
320
|
-
color: var(--fd-muted-foreground);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
307
|
.swap-picker__list {
|
|
324
308
|
flex: 1;
|
|
325
309
|
overflow-y: auto;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Select — typed wrapper over the shared `.flowdrop-input` system (base.css).
|
|
3
|
+
|
|
4
|
+
Renders the field shell (`.flowdrop-input .flowdrop-input--select`) plus a
|
|
5
|
+
built-in chevron overlay (`.flowdrop-select-wrap`), so selects match the exact
|
|
6
|
+
look of Input/Textarea. The native arrow is removed; the chevron tints to
|
|
7
|
+
--fd-primary on focus. Pass <option>/<optgroup> as children. Native attributes
|
|
8
|
+
(value, disabled, id, aria-*, onchange…) forward to the underlying <select>.
|
|
9
|
+
|
|
10
|
+
Internal for now; see Button.svelte / Input.svelte for the pattern.
|
|
11
|
+
-->
|
|
12
|
+
|
|
13
|
+
<script lang="ts">
|
|
14
|
+
import type { Snippet } from 'svelte';
|
|
15
|
+
import type { HTMLSelectAttributes } from 'svelte/elements';
|
|
16
|
+
|
|
17
|
+
// Omit native numeric `size` so our design-token size (matching Button) wins.
|
|
18
|
+
interface Props extends Omit<HTMLSelectAttributes, 'size'> {
|
|
19
|
+
/** Size — `md` is the base; `sm`/`lg` add a modifier */
|
|
20
|
+
size?: 'sm' | 'md' | 'lg';
|
|
21
|
+
/** Renders the error-border state */
|
|
22
|
+
invalid?: boolean;
|
|
23
|
+
/** Extra classes appended to the <select> */
|
|
24
|
+
class?: string;
|
|
25
|
+
/** The <option>/<optgroup> elements */
|
|
26
|
+
children: Snippet;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let { size = 'md', invalid = false, class: className = '', children, ...rest }: Props = $props();
|
|
30
|
+
|
|
31
|
+
const selectClass = $derived(
|
|
32
|
+
[
|
|
33
|
+
'flowdrop-input',
|
|
34
|
+
'flowdrop-input--select',
|
|
35
|
+
size === 'md' ? '' : `flowdrop-input--${size}`,
|
|
36
|
+
invalid ? 'flowdrop-input--invalid' : '',
|
|
37
|
+
className
|
|
38
|
+
]
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
.join(' ')
|
|
41
|
+
);
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<div class="flowdrop-select-wrap">
|
|
45
|
+
<select class={selectClass} {...rest}>
|
|
46
|
+
{@render children()}
|
|
47
|
+
</select>
|
|
48
|
+
<span class="flowdrop-select-wrap__icon" aria-hidden="true">
|
|
49
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
50
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 9l6 6 6-6" />
|
|
51
|
+
</svg>
|
|
52
|
+
</span>
|
|
53
|
+
</div>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLSelectAttributes } from 'svelte/elements';
|
|
3
|
+
interface Props extends Omit<HTMLSelectAttributes, 'size'> {
|
|
4
|
+
/** Size — `md` is the base; `sm`/`lg` add a modifier */
|
|
5
|
+
size?: 'sm' | 'md' | 'lg';
|
|
6
|
+
/** Renders the error-border state */
|
|
7
|
+
invalid?: boolean;
|
|
8
|
+
/** Extra classes appended to the <select> */
|
|
9
|
+
class?: string;
|
|
10
|
+
/** The <option>/<optgroup> elements */
|
|
11
|
+
children: Snippet;
|
|
12
|
+
}
|
|
13
|
+
declare const Select: import("svelte").Component<Props, {}, "">;
|
|
14
|
+
type Select = ReturnType<typeof Select>;
|
|
15
|
+
export default Select;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Textarea — typed wrapper over the shared `.flowdrop-input` system (base.css),
|
|
3
|
+
with the `.flowdrop-input--textarea` modifier (min-height + vertical resize).
|
|
4
|
+
|
|
5
|
+
Shares the exact field look of Input/Select so multiline fields render
|
|
6
|
+
identically. All styling lives in base.css. Native attributes (value,
|
|
7
|
+
placeholder, rows, disabled, id, aria-*, oninput…) forward to <textarea>.
|
|
8
|
+
|
|
9
|
+
Internal for now; see Button.svelte / Input.svelte for the pattern.
|
|
10
|
+
-->
|
|
11
|
+
|
|
12
|
+
<script lang="ts">
|
|
13
|
+
import type { HTMLTextareaAttributes } from 'svelte/elements';
|
|
14
|
+
|
|
15
|
+
interface Props extends HTMLTextareaAttributes {
|
|
16
|
+
/** Size — `md` is the base; `sm`/`lg` add a modifier */
|
|
17
|
+
size?: 'sm' | 'md' | 'lg';
|
|
18
|
+
/** Renders the error-border state */
|
|
19
|
+
invalid?: boolean;
|
|
20
|
+
/** Extra classes appended to the textarea */
|
|
21
|
+
class?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let { size = 'md', invalid = false, class: className = '', ...rest }: Props = $props();
|
|
25
|
+
|
|
26
|
+
const textareaClass = $derived(
|
|
27
|
+
[
|
|
28
|
+
'flowdrop-input',
|
|
29
|
+
'flowdrop-input--textarea',
|
|
30
|
+
size === 'md' ? '' : `flowdrop-input--${size}`,
|
|
31
|
+
invalid ? 'flowdrop-input--invalid' : '',
|
|
32
|
+
className
|
|
33
|
+
]
|
|
34
|
+
.filter(Boolean)
|
|
35
|
+
.join(' ')
|
|
36
|
+
);
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<textarea class={textareaClass} {...rest}></textarea>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { HTMLTextareaAttributes } from 'svelte/elements';
|
|
2
|
+
interface Props extends HTMLTextareaAttributes {
|
|
3
|
+
/** Size — `md` is the base; `sm`/`lg` add a modifier */
|
|
4
|
+
size?: 'sm' | 'md' | 'lg';
|
|
5
|
+
/** Renders the error-border state */
|
|
6
|
+
invalid?: boolean;
|
|
7
|
+
/** Extra classes appended to the textarea */
|
|
8
|
+
class?: string;
|
|
9
|
+
}
|
|
10
|
+
declare const Textarea: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type Textarea = ReturnType<typeof Textarea>;
|
|
12
|
+
export default Textarea;
|