/** * @file Number Input Node - Numeric value input with slider */ import * as React from "react"; import type { NodeDefinition, NodeRendererProps, InspectorRenderProps, ExternalDataReference, } from "../../../../../types/NodeDefinition"; import classes from "./NumberInputNode.module.css"; export type NumberInputData = { id: string; value: number; min: number; max: number; step: number; label: string; }; export const NumberInputRenderer = ({ node, isSelected, isDragging, externalData, onUpdateNode }: NodeRendererProps) => { const numberData = externalData as NumberInputData | undefined; const [value, setValue] = React.useState(numberData?.value || 0); const lastExternalValueRef = React.useRef(undefined); React.useEffect(() => { if (numberData?.value !== undefined) { setValue(numberData.value); } }, [numberData?.value]); React.useEffect(() => { if (numberData?.value === undefined) { lastExternalValueRef.current = undefined; return; } if (lastExternalValueRef.current === numberData.value) { return; } lastExternalValueRef.current = numberData.value; onUpdateNode({ data: { ...node.data, value: numberData.value, "value-output": numberData.value, }, }); }, [node.data, numberData?.value, onUpdateNode]); const handleChange = (newValue: number) => { setValue(newValue); onUpdateNode({ data: { ...node.data, value: newValue, "value-output": newValue, timestamp: Date.now(), }, }); }; return (
🔢 {numberData?.label || "Number"}
{value}
handleChange(Number(e.target.value))} className={classes.slider} />
{numberData?.min || 0} - {numberData?.max || 100}
); }; export const NumberInputInspectorRenderer = ({ externalData, onUpdateExternalData }: InspectorRenderProps) => { const numberData = externalData as NumberInputData | undefined; const [editedData, setEditedData] = React.useState({ id: numberData?.id || "", value: numberData?.value || 0, min: numberData?.min || 0, max: numberData?.max || 100, step: numberData?.step || 1, label: numberData?.label || "Number", }); const handleSave = async () => { if (onUpdateExternalData) { await onUpdateExternalData(editedData); } }; return (

Number Input

setEditedData({ ...editedData, label: e.target.value })} className={classes.input} />
setEditedData({ ...editedData, value: Number(e.target.value) })} className={classes.input} />
setEditedData({ ...editedData, min: Number(e.target.value) })} className={classes.input} />
setEditedData({ ...editedData, max: Number(e.target.value) })} className={classes.input} />
setEditedData({ ...editedData, step: Number(e.target.value) })} className={classes.input} step="0.1" />
); }; export const NumberInputNodeDefinition: NodeDefinition = { type: "number-input", displayName: "Number Input", description: "Numeric value input with configurable range", category: "Input", defaultData: { title: "Number Input", }, defaultSize: { width: 200, height: 140 }, ports: [ { id: "value-output", type: "output", label: "Value", position: "right", }, ], renderNode: NumberInputRenderer, renderInspector: NumberInputInspectorRenderer, loadExternalData: async (ref: ExternalDataReference) => { await new Promise((resolve) => setTimeout(resolve, 200)); return { id: ref.id, value: 10, min: 0, max: 100, step: 1, label: "Number", }; }, updateExternalData: async (_ref: ExternalDataReference, data: unknown) => { await new Promise((resolve) => setTimeout(resolve, 200)); console.log("Updated number input data:", data); }, };