feat: add workflow selection preservation logic and related tests
This commit is contained in:
@@ -101,6 +101,7 @@ export function WorkflowConfigPanel({
|
||||
|
||||
return (
|
||||
<div
|
||||
data-workflow-preserve-selection
|
||||
className="pointer-events-none absolute inset-y-3 right-3 z-50 flex max-w-[calc(100%-1.5rem)]"
|
||||
style={{ width: panelWidth }}
|
||||
>
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
import {
|
||||
createWorkflowNodeFromSpec,
|
||||
deleteWorkflowNode,
|
||||
shouldClearWorkflowSelectionOnPointerDown,
|
||||
updateWorkflowNodeData,
|
||||
validateWorkflowDefinition,
|
||||
type WorkflowNodeData,
|
||||
@@ -319,13 +320,24 @@ function WorkflowEditorInner({
|
||||
onSelectBranch(null)
|
||||
}
|
||||
|
||||
const clearSelectionFromCanvas = () => {
|
||||
setNodeMenu(null)
|
||||
closeConfigPanel()
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={editorRootRef}
|
||||
data-workflow-editor-root
|
||||
className="relative isolate h-full min-h-0 w-full flex-1 overflow-hidden border bg-[var(--g-editor-background)]"
|
||||
onPointerDownCapture={(event) => {
|
||||
if (shouldClearWorkflowSelectionOnPointerDown(event.target)) {
|
||||
clearSelectionFromCanvas()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div
|
||||
data-workflow-preserve-selection
|
||||
className={cn(
|
||||
"absolute left-3 top-3 z-50 flex max-w-[calc(100%-1.5rem)] flex-col items-start gap-1.5",
|
||||
selectedNodeId && "max-w-[calc(100%-25rem)]"
|
||||
@@ -348,7 +360,7 @@ function WorkflowEditorInner({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-4 left-4 z-50">
|
||||
<div data-workflow-preserve-selection className="absolute bottom-4 left-4 z-50">
|
||||
<WorkflowCanvasControls
|
||||
zoomPercent={zoomPercent}
|
||||
onZoomIn={() => playgroundTools.zoomin(true)}
|
||||
@@ -360,7 +372,7 @@ function WorkflowEditorInner({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-4 right-4 z-50">
|
||||
<div data-workflow-preserve-selection className="absolute bottom-4 right-4 z-50">
|
||||
<WorkflowEditorStatus
|
||||
validation={validation}
|
||||
nodeCount={definition.nodes.length}
|
||||
|
||||
@@ -16,6 +16,7 @@ export function WorkflowNodeCard({
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
data-workflow-preserve-selection
|
||||
className={cn(
|
||||
"group relative rounded-lg bg-[#FFFFFF] p-0.5 transition-all",
|
||||
"w-[242px]",
|
||||
|
||||
@@ -61,6 +61,7 @@ export function WorkflowPortNodeMenu({
|
||||
return (
|
||||
<div
|
||||
ref={menuRef}
|
||||
data-workflow-preserve-selection
|
||||
className="pointer-events-auto absolute z-[80] w-64 overflow-hidden rounded-lg border border-slate-200 bg-white py-1.5 shadow-[0_14px_35px_rgba(15,23,42,0.16)]"
|
||||
style={{
|
||||
left: position.x,
|
||||
|
||||
@@ -277,6 +277,21 @@ describe("workflow branch interaction helpers", () => {
|
||||
},
|
||||
}), false)
|
||||
})
|
||||
|
||||
it("clears workflow selection only when clicking outside preserved regions", async () => {
|
||||
const { shouldClearWorkflowSelectionOnPointerDown } = await loadModule()
|
||||
|
||||
assert.equal(shouldClearWorkflowSelectionOnPointerDown({
|
||||
closest(selector) {
|
||||
return selector === "[data-workflow-preserve-selection]" ? {} : null
|
||||
},
|
||||
}), false)
|
||||
assert.equal(shouldClearWorkflowSelectionOnPointerDown({
|
||||
closest() {
|
||||
return null
|
||||
},
|
||||
}), true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("workflow definition mutations", () => {
|
||||
|
||||
@@ -131,6 +131,14 @@ export function isBranchRowActionTarget(target: EventTarget | null): boolean {
|
||||
return typeof maybeElement?.closest === "function" && maybeElement.closest("button") !== null
|
||||
}
|
||||
|
||||
export function shouldClearWorkflowSelectionOnPointerDown(target: EventTarget | null): boolean {
|
||||
const maybeElement = target as { closest?: (selector: string) => Element | null } | null
|
||||
if (typeof maybeElement?.closest !== "function") {
|
||||
return false
|
||||
}
|
||||
return maybeElement.closest("[data-workflow-preserve-selection]") === null
|
||||
}
|
||||
|
||||
export function getNodeTitle(
|
||||
node: AIWorkflowDefinition["nodes"][number] | undefined,
|
||||
specs: AIWorkflowNodeSpec[] = []
|
||||
|
||||
Reference in New Issue
Block a user