Complete SQL diagnostics and editor accessibility

This commit is contained in:
2026-07-31 17:18:26 +02:00
parent 28a92feb32
commit 8724591bf9
7 changed files with 494 additions and 76 deletions
+8
View File
@@ -39,6 +39,14 @@ export type DataflowDiagnostic = {
message: string;
node_id?: string | null;
field?: string | null;
source_location?: {
start_line: number;
start_column: number;
end_line: number;
end_column: number;
start_offset?: number | null;
end_offset?: number | null;
} | null;
};
export type PipelineRevision = {
@@ -54,6 +54,7 @@ export default function DataflowCanvas({
onGraphChange,
onSelectNode
}: DataflowCanvasProps) {
const canvasRef = useRef<HTMLDivElement | null>(null);
const [instance, setInstance] = useState<ReactFlowInstance<DataflowFlowNode, Edge> | null>(null);
const [proximityEdge, setProximityEdge] = useState<Edge | null>(null);
const [selectedEdgeId, setSelectedEdgeId] = useState<string | null>(null);
@@ -129,6 +130,24 @@ export default function DataflowCanvas({
});
};
const restoreFocusAfterRemoval = (removedNodeIds: Set<string>) => {
const focusedNodeId = document.activeElement
?.closest<HTMLElement>(".react-flow__node")
?.dataset.id;
if (!focusedNodeId || !removedNodeIds.has(focusedNodeId)) return;
const removedIndex = graph.nodes.findIndex((node) => node.id === focusedNodeId);
const remainingNodes = graph.nodes.filter((node) => !removedNodeIds.has(node.id));
const nextNode = remainingNodes[Math.min(Math.max(0, removedIndex), remainingNodes.length - 1)];
requestAnimationFrame(() => {
const nextElement = nextNode
? canvasRef.current?.querySelector<HTMLElement>(
`.react-flow__node[data-id="${CSS.escape(nextNode.id)}"]`
)
: null;
(nextElement ?? canvasRef.current)?.focus();
});
};
const updateEdges = (nextEdges: Edge[]) => {
onGraphChange({
...graph,
@@ -206,7 +225,10 @@ export default function DataflowCanvas({
return (
<div
ref={canvasRef}
className="dataflow-canvas"
tabIndex={0}
aria-label="Dataflow graph canvas"
onDragOver={(event) => {
if (event.dataTransfer.types.includes("application/x-govoplan-dataflow-node")) {
event.preventDefault();
@@ -224,6 +246,11 @@ export default function DataflowCanvas({
if (readOnly) return;
const graphChanges = changes.filter((change) => change.type !== "dimensions");
if (graphChanges.length) {
restoreFocusAfterRemoval(new Set(
graphChanges
.filter((change) => change.type === "remove")
.map((change) => change.id)
));
updateNodes(applyNodeChanges(graphChanges, nodes));
}
}}
+7 -1
View File
@@ -2221,7 +2221,13 @@ function DiagnosticsPanel({
<strong>{item.code}</strong>
<small>{item.message}</small>
</span>
{item.node_id ? <code>{item.node_id}</code> : null}
{item.node_id ? (
<code>{item.node_id}</code>
) : item.source_location ? (
<code>
{item.source_location.start_line}:{item.source_location.start_column}
</code>
) : null}
</div>
))}
{nodeDiagnostics.map((item) => (
+18 -4
View File
@@ -359,6 +359,11 @@
grid-template-columns: minmax(0, 1fr) minmax(260px, 310px);
}
.dataflow-canvas:focus-visible {
outline: 2px solid var(--accent);
outline-offset: -2px;
}
.dataflow-palette,
.dataflow-inspector {
min-width: 0;
@@ -1120,7 +1125,7 @@
@media (max-width: 680px) {
.dataflow-page {
height: calc(100vh - 94px);
height: calc(100dvh - 115px);
}
.dataflow-shell {
@@ -1133,15 +1138,24 @@
border-bottom: var(--border-line);
}
.dataflow-workspace-toolbar {
overflow-x: auto;
align-items: flex-start;
scrollbar-gutter: stable;
}
.dataflow-identity-fields,
.dataflow-command-bar {
flex-wrap: wrap;
width: max-content;
min-width: max-content;
flex-wrap: nowrap;
}
.dataflow-name-input,
.dataflow-description-input {
max-width: none;
min-width: 180px;
width: 220px;
min-width: 220px;
max-width: 220px;
}
.dataflow-editor {