147 lines
4.0 KiB
TypeScript
147 lines
4.0 KiB
TypeScript
"use client";
|
|
|
|
import { Handle, useConnection } from "@xyflow/react";
|
|
|
|
import {
|
|
resolveCanvasGlowStrength,
|
|
} from "@/components/canvas/canvas-connection-magnetism";
|
|
import { useCanvasConnectionMagnetism } from "@/components/canvas/canvas-connection-magnetism-context";
|
|
import {
|
|
canvasHandleAccentColor,
|
|
canvasHandleGlowShadow,
|
|
type EdgeGlowColorMode,
|
|
} from "@/lib/canvas-utils";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
type ReactFlowHandleProps = React.ComponentProps<typeof Handle>;
|
|
|
|
type CanvasHandleProps = Omit<ReactFlowHandleProps, "id"> & {
|
|
nodeId: string;
|
|
nodeType?: string;
|
|
id?: string;
|
|
};
|
|
|
|
function normalizeHandleId(value: string | undefined): string | undefined {
|
|
return value === "" ? undefined : value;
|
|
}
|
|
|
|
export default function CanvasHandle({
|
|
nodeId,
|
|
nodeType,
|
|
id,
|
|
type,
|
|
className,
|
|
style,
|
|
...rest
|
|
}: CanvasHandleProps) {
|
|
const connection = useConnection();
|
|
const { activeTarget } = useCanvasConnectionMagnetism();
|
|
|
|
const connectionState = connection as {
|
|
inProgress?: boolean;
|
|
isValid?: boolean | null;
|
|
fromNode?: unknown;
|
|
toNode?: unknown;
|
|
fromHandle?: unknown;
|
|
toHandle?: unknown;
|
|
};
|
|
const hasConnectionPayload =
|
|
connectionState.fromNode !== undefined ||
|
|
connectionState.toNode !== undefined ||
|
|
connectionState.fromHandle !== undefined ||
|
|
connectionState.toHandle !== undefined;
|
|
const isConnectionDragActive =
|
|
connectionState.inProgress === true ||
|
|
(connectionState.inProgress === undefined && hasConnectionPayload);
|
|
|
|
const handleId = normalizeHandleId(id);
|
|
const targetHandleId = normalizeHandleId(activeTarget?.handleId);
|
|
|
|
const toNodeId =
|
|
connectionState.toNode &&
|
|
typeof connectionState.toNode === "object" &&
|
|
"id" in connectionState.toNode &&
|
|
typeof (connectionState.toNode as { id?: unknown }).id === "string"
|
|
? ((connectionState.toNode as { id: string }).id ?? null)
|
|
: null;
|
|
|
|
const toHandleMeta =
|
|
connectionState.toHandle && typeof connectionState.toHandle === "object"
|
|
? (connectionState.toHandle as { id?: string | null; type?: "source" | "target" })
|
|
: null;
|
|
const toHandleId = normalizeHandleId(
|
|
toHandleMeta?.id === null ? undefined : toHandleMeta?.id,
|
|
);
|
|
const toHandleType =
|
|
toHandleMeta?.type === "source" || toHandleMeta?.type === "target"
|
|
? toHandleMeta.type
|
|
: null;
|
|
|
|
const colorMode: EdgeGlowColorMode =
|
|
typeof document !== "undefined" && document.documentElement.classList.contains("dark")
|
|
? "dark"
|
|
: "light";
|
|
|
|
const isActiveTarget =
|
|
isConnectionDragActive &&
|
|
activeTarget !== null &&
|
|
activeTarget.nodeId === nodeId &&
|
|
activeTarget.handleType === type &&
|
|
targetHandleId === handleId;
|
|
|
|
const isNativeHoverTarget =
|
|
connectionState.inProgress === true &&
|
|
toNodeId === nodeId &&
|
|
toHandleType === type &&
|
|
toHandleId === handleId;
|
|
|
|
let glowStrength = 0;
|
|
|
|
if (isActiveTarget) {
|
|
glowStrength = resolveCanvasGlowStrength({
|
|
distancePx: activeTarget.distancePx,
|
|
});
|
|
} else if (isNativeHoverTarget) {
|
|
glowStrength = connectionState.isValid === true ? 1 : 0.68;
|
|
}
|
|
|
|
const glowState: "idle" | "near" | "snapped" =
|
|
glowStrength <= 0 ? "idle" : glowStrength >= 0.96 ? "snapped" : "near";
|
|
|
|
const accentColor = canvasHandleAccentColor({
|
|
nodeType,
|
|
handleId,
|
|
handleType: type,
|
|
});
|
|
const boxShadow = canvasHandleGlowShadow({
|
|
nodeType,
|
|
handleId,
|
|
handleType: type,
|
|
strength: glowStrength,
|
|
colorMode,
|
|
});
|
|
|
|
return (
|
|
<Handle
|
|
{...rest}
|
|
id={id}
|
|
type={type}
|
|
className={cn(
|
|
"!h-3 !w-3 !border-2 !border-background transition-[box-shadow,background-color] duration-150",
|
|
className,
|
|
)}
|
|
style={{
|
|
...style,
|
|
backgroundColor: accentColor,
|
|
boxShadow,
|
|
}}
|
|
data-node-id={nodeId}
|
|
data-handle-id={id ?? ""}
|
|
data-handle-type={type}
|
|
data-glow-state={glowState}
|
|
data-glow-strength={glowStrength.toFixed(3)}
|
|
data-glow-mode={colorMode}
|
|
/>
|
|
);
|
|
}
|