fix(forms): keep native password input for format:password; drop dead forwardRef
This commit is contained in:
@@ -87,4 +87,25 @@ describe("SchemaForm — ExpressionField-backed", () => {
|
||||
expect(screen.getByText("URL")).toBeInTheDocument();
|
||||
expect(screen.getByText("*")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("format:password fields render as a native password input, not an expression field", () => {
|
||||
const schema: JsonSchemaObject = {
|
||||
type: "object",
|
||||
properties: {
|
||||
pass: { type: "string", format: "password", title: "Password" },
|
||||
},
|
||||
};
|
||||
render(
|
||||
<SchemaForm
|
||||
schema={schema}
|
||||
value={{ pass: "hunter2" }}
|
||||
onChange={() => {}}
|
||||
previewInput={{ anything: "goes" }}
|
||||
/>,
|
||||
);
|
||||
const el = screen.getByLabelText(/password/i);
|
||||
expect(el).toHaveAttribute("type", "password");
|
||||
// No expression preview leaked even though previewInput was provided.
|
||||
expect(screen.queryByTestId("expr-preview")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { forwardRef, useImperativeHandle, useRef } from "react";
|
||||
import { useRef } from "react";
|
||||
import type { JsonSchemaObject } from "@/api/types";
|
||||
import {
|
||||
ExpressionField,
|
||||
@@ -29,23 +29,33 @@ export function SchemaForm({ schema, value, onChange, previewInput, onFieldFocus
|
||||
const id = `sf-${name}`;
|
||||
const isRequired = required.has(name);
|
||||
const raw = toEditorString(value[name]);
|
||||
const st = mapSchemaType(prop.type);
|
||||
const isSecret = prop.format === "password";
|
||||
return (
|
||||
<label key={name} htmlFor={id} className="block text-sm space-y-1">
|
||||
<span className="text-slate-300">
|
||||
{label}
|
||||
{isRequired && <span className="text-red-400 ml-0.5">*</span>}
|
||||
</span>
|
||||
<SchemaExpressionField
|
||||
id={id}
|
||||
value={raw}
|
||||
onChange={(v) => set(name, v)}
|
||||
onFieldFocus={onFieldFocus}
|
||||
previewInput={previewInput}
|
||||
schemaType={st}
|
||||
codeBlock={prop.format === "code"}
|
||||
singleLine={prop.format !== "code"}
|
||||
/>
|
||||
{isSecret ? (
|
||||
<input
|
||||
id={id}
|
||||
type="password"
|
||||
value={raw}
|
||||
onChange={(e) => set(name, e.target.value)}
|
||||
className="w-full rounded bg-slate-950 border border-slate-800 px-3 py-2 outline-none focus:border-brand-500 text-sm"
|
||||
/>
|
||||
) : (
|
||||
<SchemaExpressionField
|
||||
id={id}
|
||||
value={raw}
|
||||
onChange={(v) => set(name, v)}
|
||||
onFieldFocus={onFieldFocus}
|
||||
previewInput={previewInput}
|
||||
schemaType={mapSchemaType(prop.type)}
|
||||
codeBlock={prop.format === "code"}
|
||||
singleLine={prop.format !== "code"}
|
||||
/>
|
||||
)}
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
@@ -53,14 +63,13 @@ export function SchemaForm({ schema, value, onChange, previewInput, onFieldFocus
|
||||
);
|
||||
}
|
||||
|
||||
const SchemaExpressionField = forwardRef<ExpressionFieldHandle, {
|
||||
function SchemaExpressionField(props: {
|
||||
id: string; value: string; onChange: (v: string) => void;
|
||||
previewInput?: unknown; schemaType?: ExpressionSchemaType;
|
||||
singleLine?: boolean; codeBlock?: boolean;
|
||||
onFieldFocus?: (handle: ExpressionFieldHandle) => void;
|
||||
}>(function SchemaExpressionField(props, _outer) {
|
||||
}) {
|
||||
const ref = useRef<ExpressionFieldHandle | null>(null);
|
||||
useImperativeHandle(_outer, () => ref.current ?? { insertAtCaret: () => {}, focus: () => {} });
|
||||
return (
|
||||
<ExpressionField
|
||||
ref={ref}
|
||||
@@ -74,7 +83,7 @@ const SchemaExpressionField = forwardRef<ExpressionFieldHandle, {
|
||||
onFocus={() => { if (ref.current) props.onFieldFocus?.(ref.current); }}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function mapSchemaType(t?: string): ExpressionSchemaType {
|
||||
if (t === "integer") return "integer";
|
||||
|
||||
Reference in New Issue
Block a user