Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion client/src/components/DynamicJsonForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,12 @@ const DynamicJsonForm = forwardRef<DynamicJsonFormRef, DynamicJsonFormProps>(
if (!isNaN(num)) {
handleFieldChange(path, num);
}
clearNumericDraft(path);
// Keep the draft if the raw text differs from the
// stringified number (e.g. "1.0" vs "1") so the
// display preserves the user's decimal notation.
if (val === String(num)) {
clearNumericDraft(path);
}
}}
placeholder={propSchema.description}
required={isRequired}
Expand Down
43 changes: 39 additions & 4 deletions client/src/components/ToolsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ const ToolsTab = ({
serverSupportsTaskRequests: boolean;
}) => {
const [params, setParams] = useState<Record<string, unknown>>({});
const [numericDrafts, setNumericDrafts] = useState<Record<string, string>>(
{},
);
const [runAsTask, setRunAsTask] = useState(false);
const [isToolRunning, setIsToolRunning] = useState(false);
const [isOutputSchemaExpanded, setIsOutputSchemaExpanded] = useState(false);
Expand Down Expand Up @@ -241,6 +244,7 @@ const ToolsTab = ({
];
});
setParams(Object.fromEntries(params));
setNumericDrafts({});
const toolTaskSupport = serverSupportsTaskRequests
? getTaskSupport(selectedTool)
: "forbidden";
Expand Down Expand Up @@ -521,20 +525,29 @@ const ToolsTab = ({
name={key}
placeholder={prop.description}
value={
params[key] === undefined
? ""
: String(params[key])
Object.prototype.hasOwnProperty.call(
numericDrafts,
key,
)
? numericDrafts[key]
: params[key] === undefined
? ""
: String(params[key])
}
onChange={(e) => {
const value = e.target.value;
setNumericDrafts((prev) => ({
...prev,
[key]: value,
}));
if (value === "") {
// Field cleared - set to undefined
setParams({
...params,
[key]: undefined,
});
} else {
// Field has value - try to convert to number, but store input either way
// Field has value - try to convert to number
const num = Number(value);
if (!isNaN(num)) {
setParams({
Expand All @@ -550,6 +563,28 @@ const ToolsTab = ({
}
}
}}
onBlur={(e) => {
const val = e.target.value;
if (!val) {
setNumericDrafts((prev) => {
const next = { ...prev };
delete next[key];
return next;
});
return;
}
const num = Number(val);
if (
prop.type === "integer" ||
val === String(num)
) {
setNumericDrafts((prev) => {
const next = { ...prev };
delete next[key];
return next;
});
}
}}
className="mt-1"
/>
) : (
Expand Down
42 changes: 42 additions & 0 deletions client/src/components/__tests__/DynamicJsonForm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,48 @@ describe("DynamicJsonForm Number Fields", () => {
fireEvent.change(input, { target: { value: "-74.01" } });
expect(input.value).toBe("-74.01");
});

it("should preserve decimal zero after blur", () => {
const schema: JsonSchemaType = {
type: "number",
description: "Coordinate",
};

const WrappedForm = () => {
const [value, setValue] = useState<number>(0);
return (
<DynamicJsonForm schema={schema} value={value} onChange={setValue} />
);
};

render(<WrappedForm />);
const input = screen.getByRole("spinbutton") as HTMLInputElement;

fireEvent.change(input, { target: { value: "1.0" } });
fireEvent.blur(input);
expect(input.value).toBe("1.0");
});

it("should not preserve decimal zero for integer fields after blur", () => {
const schema: JsonSchemaType = {
type: "integer",
description: "Count",
};

const WrappedForm = () => {
const [value, setValue] = useState<number>(0);
return (
<DynamicJsonForm schema={schema} value={value} onChange={setValue} />
);
};

render(<WrappedForm />);
const input = screen.getByRole("spinbutton") as HTMLInputElement;

fireEvent.change(input, { target: { value: "1.0" } });
fireEvent.blur(input);
expect(input.value).toBe("1");
});
});
});

Expand Down