diff --git a/.changeset/multi-type-schema-support.md b/.changeset/multi-type-schema-support.md
new file mode 100644
index 00000000..78aafece
--- /dev/null
+++ b/.changeset/multi-type-schema-support.md
@@ -0,0 +1,7 @@
+---
+"json-schema-studio": patch
+---
+
+Fix: Handle multi-type schemas in node type inference
+
+Multi-type schemas (like `["string", "null"]`) were previously failing type inference and falling back to a default "others" node style. The type inference logic has been updated to pick the first non-null type from the array to determine the node's visual style.
diff --git a/src/components/CustomReactFlowNode.tsx b/src/components/CustomReactFlowNode.tsx
index f612998c..5f3cffee 100644
--- a/src/components/CustomReactFlowNode.tsx
+++ b/src/components/CustomReactFlowNode.tsx
@@ -1,5 +1,6 @@
import { Handle } from "@xyflow/react";
import type { RFNodeData } from "../utils/processAST";
+import { neonColors } from "../utils/processAST";
import { useContext, useLayoutEffect, useRef, useState } from "react";
import { AppContext } from "../contexts/AppContext";
@@ -30,13 +31,14 @@ const CustomNode = ({ data, id, selected }: { data: RFNodeData; id: string; sele
setHandleOffsets(offsets);
}, [data.nodeData]);
+ const { color } = data.nodeStyle;
+
return (
@@ -64,12 +66,12 @@ const CustomNode = ({ data, id, selected }: { data: RFNodeData; id: string; sele
{data.nodeLabel}
@@ -96,19 +98,28 @@ const CustomNode = ({ data, id, selected }: { data: RFNodeData; id: string; sele
{isArray ? (
-
- {(keyData.value as string[]).map((item, index) => (
-
{
- rowRefs.current[`${id}-${item}`] = el;
- }}
- key={index}
- className="px-2 py-[2px] bg-[var(--node-value-bg-color)]"
- style={{ border: `1px solid ${data.nodeStyle.color}30` }}
- >
- {item}
-
- ))}
+
+ {(keyData.value as string[]).map((item, index) => {
+ const itemColor = key === "type" && item in neonColors
+ ? neonColors[item as keyof typeof neonColors]
+ : null;
+ return (
+
{
+ rowRefs.current[`${id}-${item}`] = el;
+ }}
+ key={index}
+ className={`px-2 py-[2px] ${itemColor ? "rounded text-center font-medium shadow-sm" : "bg-[var(--node-value-bg-color)]"}`}
+ style={{
+ border: itemColor ? `1px solid ${itemColor}` : `1px solid ${color}30`,
+ backgroundColor: itemColor ? `${itemColor}33` : undefined,
+ color: itemColor ? (theme === "dark" ? itemColor : `color-mix(in srgb, ${itemColor} 60%, black)`) : undefined,
+ }}
+ >
+ {item}
+
+ );
+ })}
) : (
{
- if (typeof nodeData.type?.value === "string") {
- return ["objectSchema", nodeData.type.value as string];
+export const inferSchemaType = (nodeData: GraphNode["data"]["nodeData"]): [string, string, string[]?] => {
+ const typeValue = nodeData.type?.value;
+
+ if (typeof typeValue === "string") {
+ return ["objectSchema", typeValue];
+ }
+
+ if (Array.isArray(typeValue)) {
+ let primaryType = "others";
+ for (const t of typeValue as string[]) {
+ if (t !== "null") {
+ primaryType = t;
+ break;
+ }
+ }
+ return ["multiType", primaryType, typeValue as string[]];
}
const hasAnyKeyword = (keywords: Set) => {
diff --git a/src/utils/processAST.ts b/src/utils/processAST.ts
index bfd1c23f..0b06a6a6 100644
--- a/src/utils/processAST.ts
+++ b/src/utils/processAST.ts
@@ -32,7 +32,7 @@ export type RFNodeData = {
}
type NodeStyle = {
- color: string
+ color: string;
}
export type GraphEdge = RFEdge & {
@@ -80,7 +80,7 @@ type GenerateSourceHandles = (key: string | undefined, value: unknown, nodeId: s
type UpdateNode = (node: UnpositionedGraphNode, update: UpdateNodeOptionalParameters) => void;
-const neonColors = {
+export const neonColors = {
string: "#FF6EFF", // neon magenta
number: "#00FF95", // neon mint
integer: "#00FF95", // neon mint
@@ -91,6 +91,7 @@ const neonColors = {
booleanSchemaTrue: "#12FF4B", // neon green
booleanSchemaFalse: "#FF3B3B", // neon red
reference: "#FFE1BD", // soft neon cream
+ multiType: "#FF007F", // neon rose
others: "#CCCCCC", // soft gray
};
@@ -157,7 +158,8 @@ export const processAST: ProcessAST = ({ ast, schemaUri, nodes, edges, parentId,
}
const getColor = (nodeData: NodeData) => {
- const [, definedFor] = inferSchemaType(nodeData);
+ const [schemaType, definedFor] = inferSchemaType(nodeData);
+ if (schemaType === "multiType") return neonColors.multiType;
return (
neonColors[definedFor as keyof typeof neonColors] ?? neonColors.others
);
@@ -179,7 +181,7 @@ export const processAST: ProcessAST = ({ ast, schemaUri, nodes, edges, parentId,
updateNode(
newNode,
- { nodeData, nodeStyle: { color: color }, addTargetHandle: { handleId: targetHandle, position: Position.Left } }
+ { nodeData, nodeStyle: { color }, addTargetHandle: { handleId: targetHandle, position: Position.Left } }
);
};