build: 清理 tinyflow-ui 构建告警

- 收口 Svelte props 响应式引用与 custom element props 声明

- 清理局部未使用样式与类型告警并保持现有业务语义
This commit is contained in:
2026-05-06 19:22:33 +08:00
parent ba70fec9a5
commit 8d07b306e5
34 changed files with 288 additions and 151 deletions

View File

@@ -1,10 +1,10 @@
<svelte:options customElement={{
tag: "tinyflow-component",
shadow: "none",
// props: {
// options: { reflect: true, type: 'Object', attribute: 'options' },
// onInit: { reflect: true, type: 'Object', attribute: 'onInit' }
// },
props: {
options: { type: 'Object', attribute: 'options' },
onInit: { type: 'Object', attribute: 'on-init' }
},
}} />
<script lang="ts">
@@ -14,31 +14,50 @@
import type {TinyflowData, TinyflowOptions} from '#types';
import {setContext} from 'svelte';
const { options, onInit }: {
const props = $props<{
options: TinyflowOptions,
onInit: (svelteFlow: ReturnType<typeof useSvelteFlow>) => void,
} = $props();
}>();
let { data } = options;
let initialViewport = null;
if (typeof data === 'string') {
try {
data = JSON.parse(data.trim());
} catch (e) {
console.error('Invalid JSON data:', data);
const parseData = (source: TinyflowOptions['data']) => {
let nextData = source;
if (typeof nextData === 'string') {
try {
nextData = JSON.parse(nextData.trim());
} catch (error) {
console.error('Invalid JSON data:', nextData, error);
}
}
}
initialViewport = (data as TinyflowData)?.viewport || null;
return nextData as TinyflowData | null | undefined;
};
const getOptions = () => props.options;
const contextOptions = new Proxy({} as TinyflowOptions, {
get(_target, property) {
return (getOptions() as Record<PropertyKey, unknown>)[property];
},
has(_target, property) {
return property in getOptions();
},
ownKeys() {
return Reflect.ownKeys(getOptions());
},
getOwnPropertyDescriptor(_target, property) {
const descriptor = Object.getOwnPropertyDescriptor(getOptions(), property);
return descriptor ? { ...descriptor, configurable: true } : undefined;
}
}) as TinyflowOptions;
const data = parseData(getOptions().data);
const initialViewport = data?.viewport || null;
store.init(
(data as TinyflowData)?.nodes || [],
(data as TinyflowData)?.edges || [],
data?.nodes || [],
data?.edges || [],
initialViewport,
);
setContext('tinyflow_options', options);
setContext('tinyflow_options', contextOptions);
</script>
<SvelteFlowProvider>
<TinyflowCore {onInit} />
<TinyflowCore onInit={props.onInit} />
</SvelteFlowProvider>

View File

@@ -46,8 +46,6 @@
const { onInit }: { onInit: any; [key: string]: any } = $props();
const svelteFlow = useSvelteFlow();
onInit(svelteFlow);
let showEdgePanel = $state(false);
let currentEdge = $state<Edge | null>(null);
let nodePickerVisible = $state(false);
@@ -342,7 +340,7 @@
{ duration: 180 }
);
} else {
svelteFlow.zoomTo(zoom, { duration: 180 });
(svelteFlow as any).zoomTo?.(zoom, { duration: 180 });
}
}
}
@@ -726,6 +724,7 @@
}
onMount(() => {
onInit(svelteFlow);
store.updateEdges((edges) => edges.map((edge) => ensureEdgeVisualDefaults(edge)));
repairOrphanParentNodes();
if (!readonly) {

View File

@@ -1,18 +1,30 @@
<svelte:options customElement={{ props: {} }} />
<script lang="ts">
import type {MyHTMLButtonAttributes} from './types';
import type {Snippet} from 'svelte';
const { children, primary, onclick, ...rest }: MyHTMLButtonAttributes & {
children?: Snippet;
primary?: boolean;
} = $props();
const {
children,
primary,
onclick,
class: className = '',
style = '',
...rest
}: Omit<MyHTMLButtonAttributes, 'class' | 'style'> & {
children?: Snippet;
primary?: boolean;
class?: string;
style?: string;
} = $props();
</script>
<button
type="button"
{...rest}
onclick={onclick}
class="tf-btn {primary?'tf-btn-primary':''} nopan nodrag {rest.class}"
{style}
class="tf-btn {primary?'tf-btn-primary':''} nopan nodrag {className}"
>
{@render children?.()}
</button>

View File

@@ -1,3 +1,5 @@
<svelte:options customElement={{ props: {} }} />
<script lang="ts">
import type {MyHTMLInputAttributes} from './types';

View File

@@ -1,17 +1,30 @@
<svelte:options customElement={{ props: {} }} />
<script lang="ts">
import {Button, Input} from './index';
import type {MyHTMLAttributes} from './types';
const { placeholder, label, value, buttonText = "选择...",onChosen, ...rest }: {
placeholder?: string;
label?: any;
value?: any;
buttonText?:string
onChosen?: (value?: any, label?: any, event?: Event) => void,
} & MyHTMLAttributes = $props();
const {
placeholder,
label,
value,
buttonText = "选择...",
onChosen,
class: className = '',
style = '',
...rest
}: Omit<MyHTMLAttributes, 'class' | 'style'> & {
placeholder?: string;
label?: any;
value?: any;
buttonText?: string;
onChosen?: (value?: any, label?: any, event?: Event) => void;
class?: string;
style?: string;
} = $props();
</script>
<div {...rest} class="tf-chosen nopan nodrag {rest.class}">
<div {...rest} {style} class="tf-chosen nopan nodrag {className}">
<input type="hidden" value={value}>
<Input value={label} {placeholder} style="flex-grow: 1;" disabled/>
<Button onclick={(e)=>{

View File

@@ -1,3 +1,5 @@
<svelte:options customElement={{ props: {} }} />
<script lang="ts">
import type {Snippet} from 'svelte';
import {Render} from './index';

View File

@@ -1,6 +1,28 @@
<svelte:options customElement={{ props: {} }} />
<script lang="ts">
import type {MyHTMLInputAttributes} from './types';
const { ...rest }: MyHTMLInputAttributes = $props();
const {
class: className = '',
style = '',
type = 'text',
variant = 'default',
value,
...rest
}: Omit<MyHTMLInputAttributes, 'class' | 'style' | 'type' | 'value'> & {
class?: string;
style?: string;
type?: string;
value?: unknown;
variant?: 'default' | 'borderless';
} = $props();
</script>
<input type="text" spellcheck="false" {...rest} class="tf-input nopan nodrag {rest.class}" />
<input
{type}
spellcheck="false"
{...rest}
{value}
{style}
class="tf-input {variant === 'borderless' ? 'tf-input-borderless' : ''} nopan nodrag {className}"
/>

View File

@@ -1,11 +1,24 @@
<svelte:options customElement={{ props: {} }} />
<script lang="ts">
import Button from './button.svelte';
import type {MyHTMLButtonAttributes} from './types';
const { ...rest }: MyHTMLButtonAttributes = $props();
const {
class: className = '',
style,
...rest
}: MyHTMLButtonAttributes & {
class?: string | null;
style?: string | null;
} = $props();
</script>
<Button {...rest} class="input-btn-more {rest.class}">
<Button
{...rest}
class="input-btn-more {className}"
style={style ?? undefined}
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path
d="M4.5 10.5C3.675 10.5 3 11.175 3 12C3 12.825 3.675 13.5 4.5 13.5C5.325 13.5 6 12.825 6 12C6 11.175 5.325 10.5 4.5 10.5ZM19.5 10.5C18.675 10.5 18 11.175 18 12C18 12.825 18.675 13.5 19.5 13.5C20.325 13.5 21 12.825 21 12C21 11.175 20.325 10.5 19.5 10.5ZM12 10.5C11.175 10.5 10.5 11.175 10.5 12C10.5 12.825 11.175 13.5 12 13.5C12.825 13.5 13.5 12.825 13.5 12C13.5 11.175 12.825 10.5 12 10.5Z"></path>

View File

@@ -29,8 +29,9 @@
let floatingRef: any = $state();
let hoveredItem: SelectItem | null = $state(null);
let isOpen = $state(false);
let selectedItem = $state<SelectItem | null>(null);
let selectedItem = $derived.by(() => {
$effect(() => {
let found: SelectItem | null = null;
const findItem = (items: SelectItem[]) => {
for (const it of items) {
@@ -41,8 +42,15 @@
}
};
findItem(refOptions);
return found;
selectedItem = found;
});
let selectedNodeType = $derived(selectedItem?.nodeType);
let selectedNodeIcon = $derived(
selectedNodeType ? nodeIcons[selectedNodeType] : undefined,
);
let selectedLabel = $derived(
selectedItem?.displayLabel || selectedItem?.label || '',
);
function closeMenu() {
floatingRef?.hide();
@@ -111,12 +119,12 @@
<div class="tf-mixed-box tf-mixed-ref-box">
{#if selectedItem}
<div class="tf-mixed-sel-val">
{#if selectedItem.nodeType && nodeIcons[selectedItem.nodeType]}
{#if selectedNodeType && selectedNodeIcon}
<span class="tf-mixed-val-icon">
{@html nodeIcons[selectedItem.nodeType]}
{@html selectedNodeIcon}
</span>
{/if}
<span class="tf-mixed-val-name">{selectedItem.displayLabel || selectedItem.label}</span>
<span class="tf-mixed-val-name">{selectedLabel}</span>
</div>
{:else}
<div class="tf-mixed-placeholder">{placeholder}</div>

View File

@@ -1,6 +1,10 @@
<svelte:options customElement={{ props: {} }} />
<script lang="ts">
let { target } = $props();
if (typeof target === 'undefined') target = "undefined";
const props = $props<{ target?: string | (() => unknown) }>();
const target = $derived(
typeof props.target === 'undefined' ? 'undefined' : props.target
);
</script>
@@ -9,4 +13,3 @@
{:else }
{@html target}
{/if}

View File

@@ -1,3 +1,5 @@
<svelte:options customElement={{ props: {} }} />
<script lang="ts">
import {FloatingTrigger} from './index';
import type {SelectItem} from '#types';
@@ -541,6 +543,7 @@
color: var(--tf-text-secondary);
line-height: 1.3;
margin-top: 2px;
line-clamp: 2;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;

View File

@@ -1,14 +1,27 @@
<svelte:options customElement={{ props: {} }} />
<script lang="ts">
import type {MyHTMLTextareaAttributes} from './types';
const { value, height, autoHeight = true, maxHeight, onHeightChange, ...rest }: MyHTMLTextareaAttributes & {
value?: any;
height?: string | number;
autoHeight?: boolean;
rows?: number;
maxHeight?: string | number;
onHeightChange?: (height: string) => void;
} = $props();
const {
value,
height,
autoHeight = true,
maxHeight,
onHeightChange,
class: className = '',
style = '',
...rest
}: Omit<MyHTMLTextareaAttributes, 'value' | 'class' | 'style'> & {
value?: unknown;
class?: string;
style?: string;
height?: string | number;
autoHeight?: boolean;
rows?: number;
maxHeight?: string | number;
onHeightChange?: (height: string) => void;
} = $props();
let textareaEl: HTMLTextAreaElement;
let defaultHeight: number;
@@ -69,6 +82,7 @@
bind:this={textareaEl}
spellcheck="false"
{...rest}
{style}
oninput={(e)=>{
adjustHeight();
rest.oninput?.(e);
@@ -77,5 +91,5 @@
adjustHeight();
rest.onchange?.(e);
}}
class="tf-textarea nodrag nowheel {rest.class}"
>{value || ""}</textarea>
class="tf-textarea nodrag nowheel {className}"
>{String(value ?? '')}</textarea>

View File

@@ -1,3 +1,5 @@
<svelte:options customElement={{ props: {} }} />
<script lang="ts">
import {onMount} from 'svelte';
import {Compartment, EditorState, type Transaction} from '@codemirror/state';

View File

@@ -79,7 +79,7 @@
});
triggerObject?.hide();
};
let selectItems = useRefOptions(useChildrenOnly);
let selectItems = useRefOptions(() => useChildrenOnly === true);
</script>
@@ -175,4 +175,3 @@
</style>

View File

@@ -275,13 +275,6 @@
align-items: center;
}
.input-item-inline {
display: flex;
align-items: center;
font-size: 12px;
color: var(--tf-text-secondary);
}
.input-more-setting {
display: flex;
flex-direction: column;

View File

@@ -14,6 +14,7 @@
import {useCopyNode} from '../utils/useCopyNode.svelte';
import {getOptions} from '../utils/NodeUtils';
import {getCurrentNodeId} from '#components/utils/NodeUtils';
import type {TinyflowNodeData} from '#types';
const {
data,
@@ -32,7 +33,7 @@
wrapperClass = '',
onCollapse
}: {
data: NodeProps['data'],
data: TinyflowNodeData,
id?: NodeProps['id'],
icon?: Snippet,
handle?: Snippet,
@@ -49,7 +50,7 @@
onCollapse?: (key: string) => void,
} = $props();
let activeKeys = data.expand ? ['key'] : [];
const activeKeys = $derived.by(() => data.expand ? ['key'] : []);
const { updateNodeData, getNode } = useSvelteFlow();
const updateNodeInternals = useUpdateNodeInternals();

View File

@@ -1,3 +1,5 @@
<svelte:options customElement={{ props: {} }} />
<script lang="ts">
import {defaultKeymap, history, historyKeymap} from '@codemirror/commands';
import {Compartment, EditorState, type Extension} from '@codemirror/state';

View File

@@ -82,7 +82,7 @@
});
triggerObject?.hide();
};
let selectItems = useRefOptions(useChildrenOnly);
let selectItems = useRefOptions(() => useChildrenOnly === true);
</script>

View File

@@ -1,3 +1,5 @@
<svelte:options customElement={{ props: {} }} />
<script lang="ts">
import NodeWrapper from '../core/NodeWrapper.svelte';
import {type NodeProps, useNodesData, useSvelteFlow} from '@xyflow/svelte';
@@ -7,11 +9,11 @@
import {useAddParameter} from '../utils/useAddParameter.svelte';
import OutputDefList from '../core/OutputDefList.svelte';
import {onMount} from 'svelte';
import type {SelectItem} from '#types';
import type {SelectItem, TinyflowNodeData} from '#types';
import CodeScriptEditor from '../core/CodeScriptEditor.svelte';
const { data, ...rest }: {
data: NodeProps['data'],
data: TinyflowNodeData,
[key: string]: any
} = $props();

View File

@@ -1,3 +1,5 @@
<svelte:options customElement={{ props: {} }} />
<script lang="ts">
import NodeWrapper from '../core/NodeWrapper.svelte';
import {
@@ -669,7 +671,20 @@
style="flex: 1; height: 100%; min-width: 0;"
/>
{:else}
<div class="condition-branch-name" style="padding-left: 8px" ondblclick={(e) => { e.stopPropagation(); editingBranchId = branch.id; }}>
<div
class="condition-branch-name"
style="padding-left: 8px"
role="button"
tabindex="0"
ondblclick={(e) => { e.stopPropagation(); editingBranchId = branch.id; }}
onkeydown={(e) => {
e.stopPropagation();
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
editingBranchId = branch.id;
}
}}
>
{branch.label || `条件分支${index + 1}`}
</div>
{/if}
@@ -1181,26 +1196,6 @@
height: 32px;
}
.input-more-setting {
display: flex;
flex-direction: column;
gap: 10px;
padding: 10px;
background: var(--tf-bg-surface);
border: 1px solid var(--tf-border-color);
border-radius: 6px;
width: 180px;
box-shadow: var(--tf-shadow-medium);
.input-more-item {
display: flex;
flex-direction: column;
gap: 4px;
font-size: 12px;
color: var(--tf-text-secondary);
}
}
.condition-expression-panel {
min-height: 140px;
display: flex;

View File

@@ -1,3 +1,5 @@
<svelte:options customElement={{ props: {} }} />
<script lang="ts">
import NodeWrapper from '../core/NodeWrapper.svelte';
import {type NodeProps, useSvelteFlow} from '@xyflow/svelte';
@@ -7,12 +9,12 @@
import {useAddParameter} from '../utils/useAddParameter.svelte';
import OutputDefList from '../core/OutputDefList.svelte';
import ConfirmParameterList from '../core/ConfirmParameterList.svelte';
import type {Parameter} from '#types';
import type {Parameter, TinyflowNodeData} from '#types';
import {deepEqual} from '#components/utils/deepEqual';
const { data, ...rest }: {
data: NodeProps['data'],
data: TinyflowNodeData,
[key: string]: any
} = $props();
@@ -94,7 +96,7 @@
message: e.target.value
}
})
}} value={data.message as string||""} />
}} value={String(data.message || '')} />
</div>
@@ -128,5 +130,3 @@
</style>

View File

@@ -1,3 +1,5 @@
<svelte:options customElement={{ props: {} }} />
<script lang="ts">
import NodeWrapper from '../core/NodeWrapper.svelte';
import {type Node, type NodeProps, useNodesData, useSvelteFlow} from '@xyflow/svelte';
@@ -8,17 +10,24 @@
import {getOptions} from '../utils/NodeUtils';
import OutputDefList from '../core/OutputDefList.svelte';
import ParamTokenEditor from '../core/ParamTokenEditor.svelte';
import type {TinyflowNodeData} from '#types';
import {onMount} from 'svelte';
const { data, ...rest }: {
data: NodeProps['data'],
const props = $props<{
data: TinyflowNodeData,
[key: string]: any
} = $props();
}>();
const data = $derived(props.data);
const currentNodeId = getCurrentNodeId();
let currentNode = useNodesData(currentNodeId);
const { addParameter } = useAddParameter();
const flowInstance = useSvelteFlow();
const { updateNodeData: updateNodeDataInner } = flowInstance;
const getRestProps = () => {
const { data: _data, ...rest } = props;
return rest;
};
const editorParameters = $derived.by(() => {
return (currentNode?.current?.data?.parameters as Array<any>) || data.parameters || [];
});
@@ -49,19 +58,22 @@
updateFormValue(form, (event.target as any)?.value);
};
const node = {
...rest,
const buildNode = (nextData = data) => ({
...getRestProps(),
id: currentNodeId,
data
} as Node;
data: nextData
} as Node);
const externalElement = document.createElement('div') as HTMLElement;
const options = getOptions();
const customNode = options.customNodes![rest.type as string];
customNode.render?.(externalElement, node, flowInstance);
const forms = customNode.forms;
const customNode = $derived.by(() => options.customNodes![getRestProps().type as string]);
const forms = $derived.by(() => customNode.forms);
let container: HTMLElement;
onMount(() => {
customNode.render?.(externalElement, buildNode(), flowInstance);
});
let container = $state<HTMLElement | null>(null);
$effect(() => {
// 注意:由于 $effect 的 state 自动追踪问题,需要 data.expand 方在 if 里的最前面
if (data.expand && container) {
@@ -71,7 +83,7 @@
$effect(() => {
if (data) {
customNode.onUpdate?.(externalElement, { ...node, data });
customNode.onUpdate?.(externalElement, buildNode(data));
}
});
@@ -94,7 +106,7 @@
</script>
<NodeWrapper data={{...data, description: customNode.description}} {...rest}>
<NodeWrapper data={{...data, description: customNode.description}} {...getRestProps()}>
{#snippet icon()}
{@html customNode.icon}
@@ -285,6 +297,7 @@
background: var(--tf-slider-track-bg);
border-radius: 2px;
outline: none;
appearance: none;
-webkit-appearance: none;
}

View File

@@ -1,3 +1,5 @@
<svelte:options customElement={{ props: {} }} />
<script lang="ts">
import NodeWrapper from '../core/NodeWrapper.svelte';
import {type NodeProps} from '@xyflow/svelte';
@@ -5,9 +7,10 @@
import {getCurrentNodeId} from '#components/utils/NodeUtils';
import RefParameterList from '../core/RefParameterList.svelte';
import {useAddParameter} from '../utils/useAddParameter.svelte';
import type {TinyflowNodeData} from '#types';
const { data, ...rest }: {
data: NodeProps['data'],
data: TinyflowNodeData,
[key: string]: any
} = $props();
@@ -46,5 +49,3 @@
}
</style>

View File

@@ -1,3 +1,5 @@
<svelte:options customElement={{ props: {} }} />
<script lang="ts">
import NodeWrapper from '../core/NodeWrapper.svelte';
import {type NodeProps, useNodesData, useSvelteFlow} from '@xyflow/svelte';
@@ -10,9 +12,10 @@
// 添加生命周期函数
import {onMount} from 'svelte';
import ParamTokenEditor from '../core/ParamTokenEditor.svelte';
import type {TinyflowNodeData} from '#types';
const { data, ...rest }: {
data: NodeProps['data'],
data: TinyflowNodeData,
[key: string]: any
} = $props();
@@ -77,7 +80,7 @@
return (currentNode?.current?.data?.parameters as Array<any>) || data.parameters || [];
});
const bodyMethods = new Set(['post', 'put', 'delete', 'patch']);
const showBodyConfig = $derived(bodyMethods.has((data.method || '').toLowerCase()));
const showBodyConfig = $derived(bodyMethods.has(String(data.method || '').toLowerCase()));
</script>
@@ -215,7 +218,7 @@
rows={5}
style="width: 100%"
parameters={editorParameters}
placeholder="请输入 json 信息" value={data.bodyJson}
placeholder="请输入 json 信息" value={String(data.bodyJson || '')}
oninput={(e:any)=>{
updateNodeData(currentNodeId,{
bodyJson: e.target.value,
@@ -233,7 +236,7 @@
rows={5}
style="width: 100%"
parameters={editorParameters}
placeholder="请输入请求信息" value={data.bodyRaw}
placeholder="请输入请求信息" value={String(data.bodyRaw || '')}
oninput={(e:any)=>{
updateNodeData(currentNodeId,{
bodyRaw: e.target.value,

View File

@@ -1,3 +1,5 @@
<svelte:options customElement={{ props: {} }} />
<script lang="ts">
import NodeWrapper from '../core/NodeWrapper.svelte';
import {type NodeProps, useNodesData, useStore, useSvelteFlow} from '@xyflow/svelte';
@@ -7,7 +9,7 @@
import {getOptions} from '../utils/NodeUtils';
import {onMount} from 'svelte';
import OutputDefList from '../core/OutputDefList.svelte';
import type {Parameter, SelectItem} from '#types';
import type {Parameter, SelectItem, TinyflowNodeData} from '#types';
import ParamTokenEditor from '../core/ParamTokenEditor.svelte';
import {
buildEditorReferenceParameters,
@@ -17,7 +19,7 @@
} from '../../utils/workflowNodeFields';
const { data, ...rest }: {
data: NodeProps['data'],
data: TinyflowNodeData,
[key: string]: any
} = $props();

View File

@@ -1,3 +1,5 @@
<svelte:options customElement={{ props: {} }} />
<script lang="ts">
import NodeWrapper from '../core/NodeWrapper.svelte';
import {type NodeProps, useNodesData, useStore, useSvelteFlow} from '@xyflow/svelte';
@@ -177,16 +179,6 @@
}
});
$effect(() => {
const validIds = new Set(queryContextOptions.map((item) => String(item.value)));
const normalized = queryContextNodeIds.filter((item) => validIds.has(item));
if (normalized.length !== queryContextNodeIds.length) {
updateNodeData(currentNodeId, {
queryContextNodeIds: normalized
});
}
});
const toggleQueryContextNode = (nodeId: string) => {
const currentIds = [...queryContextNodeIds];
const exists = currentIds.includes(nodeId);
@@ -404,6 +396,7 @@
background: var(--tf-slider-track-bg);
border-radius: 2px;
outline: none;
appearance: none;
-webkit-appearance: none;
}

View File

@@ -1,3 +1,5 @@
<svelte:options customElement={{ props: {} }} />
<script lang="ts">
import NodeWrapper from '../core/NodeWrapper.svelte';
import {Handle, type NodeProps, Position} from '@xyflow/svelte';
@@ -5,9 +7,10 @@
import RefParameterList from '../core/RefParameterList.svelte';
import {getCurrentNodeId} from '#components/utils/NodeUtils';
import {useAddParameter} from '../utils/useAddParameter.svelte';
import type {TinyflowNodeData} from '#types';
const { data, ...rest }: {
data: NodeProps['data'],
data: TinyflowNodeData,
[key: string]: any
} = $props();

View File

@@ -1,3 +1,5 @@
<svelte:options customElement={{ props: {} }} />
<script lang="ts">
import NodeWrapper from '../core/NodeWrapper.svelte';
import {type NodeProps, useNodesData, useSvelteFlow} from '@xyflow/svelte';
@@ -8,11 +10,11 @@
import {getOptions} from '../utils/NodeUtils';
import {onMount} from 'svelte';
import OutputDefList from '../core/OutputDefList.svelte';
import type {SelectItem} from '#types';
import type {SelectItem, TinyflowNodeData} from '#types';
import ParamTokenEditor from '../core/ParamTokenEditor.svelte';
const { data, ...rest }: {
data: NodeProps['data'],
data: TinyflowNodeData,
[key: string]: any
} = $props();
@@ -109,9 +111,9 @@
placeholder="请输入关键字"
style="width: 100%"
parameters={editorParameters}
value={data.keyword || ''}
oninput={(e)=>{
const newValue = e.target.value;
value={String(data.keyword || '')}
oninput={(e: Event)=>{
const newValue = (e.target as HTMLInputElement).value;
updateNodeData(currentNodeId, ()=>{
return {
keyword: newValue
@@ -127,10 +129,10 @@
mode="input"
placeholder="搜索的数据条数"
style="width: 100%"
value={data.limit || ''}
value={String(data.limit || '')}
parameters={editorParameters}
oninput={(e)=>{
const newValue = e.target.value;
oninput={(e: Event)=>{
const newValue = (e.target as HTMLInputElement).value;
updateNodeData(currentNodeId, ()=>{
return {
limit: newValue
@@ -170,4 +172,3 @@
}
</style>

View File

@@ -1,3 +1,5 @@
<svelte:options customElement={{ props: {} }} />
<script lang="ts">
import NodeWrapper from '../core/NodeWrapper.svelte';
import {Heading, Input, Textarea} from '../base';
@@ -12,9 +14,10 @@
normalizeStartNodeData,
normalizeStartFormMeta,
} from '../../utils/workflowNodeFields';
import type {TinyflowNodeData} from '#types';
const { data, ...rest }: {
data: NodeProps['data'],
data: TinyflowNodeData,
[key: string]: any
} = $props();

View File

@@ -1,3 +1,5 @@
<svelte:options customElement={{ props: {} }} />
<script lang="ts">
import NodeWrapper from '../core/NodeWrapper.svelte';
import {type NodeProps, useSvelteFlow} from '@xyflow/svelte';
@@ -7,9 +9,10 @@
import {getCurrentNodeId} from '#components/utils/NodeUtils';
import {useAddParameter} from '../utils/useAddParameter.svelte';
import OutputDefList from '../core/OutputDefList.svelte';
import type {TinyflowNodeData} from '#types';
const { data, ...rest }: {
data: NodeProps['data'],
data: TinyflowNodeData,
[key: string]: any
} = $props();
@@ -62,7 +65,7 @@
template: e.target.value
}
})
}} value={data.template ||""} />
}} value={String(data.template || '')} />
</div>
@@ -89,5 +92,3 @@
</style>

View File

@@ -144,10 +144,16 @@ const nodeToOptions = (
}
};
export const useRefOptions: any = (useChildrenOnly: boolean = false) => {
export const useRefOptions: any = (
useChildrenOnly: boolean | (() => boolean) = false,
) => {
const currentNodeId = getCurrentNodeId();
const currentNode = useNodesData(currentNodeId);
const { nodes, edges, nodeLookup } = $derived(useStore());
const isChildrenOnly = () =>
typeof useChildrenOnly === 'function'
? useChildrenOnly()
: useChildrenOnly;
let selectItems = $derived.by(() => {
const resultOptions = [];
@@ -158,7 +164,7 @@ export const useRefOptions: any = (useChildrenOnly: boolean = false) => {
//通过 nodeLookup.get 才会得到有 parentId 的 node
const cNode = nodeLookup.get(currentNodeId)!;
if (useChildrenOnly) {
if (isChildrenOnly()) {
for (const node of nodes) {
const nodeIsChildren = node.parentId === currentNode.current.id;
if (nodeIsChildren) {

View File

@@ -4,6 +4,7 @@ import type { Node, useSvelteFlow } from '@xyflow/svelte';
export type TinyflowData = Partial<
ReturnType<ReturnType<typeof useSvelteFlow>['toObject']>
>;
export type TinyflowNodeData = Record<string, any>;
export type TinyflowTheme = 'light' | 'dark';
export type SelectItem = {

View File

@@ -897,7 +897,10 @@ describe('workflow node fields', () => {
expect(normalizedWorkflow.nodes[0]?.data?.parameters?.[0]?.required).toBe(
true,
);
expect(normalizedWorkflow.nodes[0]?.data?.startFormSchema?.[0]?.key).toBe(
expect(
(normalizedWorkflow.nodes[0]?.data as Record<string, any> | undefined)
?.startFormSchema?.[0]?.key,
).toBe(
'user_input',
);
expect(normalizedWorkflow.nodes[1]?.data?.parameters).toEqual([]);

View File

@@ -1237,7 +1237,10 @@ function replaceStartFieldReferenceValue(
return value;
}
function collectDownstreamNodeIds(rootNodeId: string, edges: Edge[]) {
export function collectDownstreamNodeIds(
rootNodeId: string,
edges: Edge[],
) {
const nodeIds = new Set<string>();
const visit = (nodeId: string) => {
@@ -1281,7 +1284,7 @@ export function renameStartFieldReferencesInNodes(
const oldRefPath = `${normalizedStartNodeId}.${normalizedCurrentKey}`;
const newRefPath = `${normalizedStartNodeId}.${normalizedNextKey}`;
const nextNodes = nodes.map((node) => {
const nextNodes: Node[] = nodes.map((node) => {
if (node.id === normalizedStartNodeId) {
return node;
}
@@ -1295,7 +1298,7 @@ export function renameStartFieldReferencesInNodes(
}
return {
...node,
data: nextData,
data: nextData ?? {},
};
});