mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-08-15 02:53:25 +10:00
fix: clamp page margin values to [0, 100] to prevent crash on paste (#3277)
Co-authored-by: Amruth Pillai <im.amruth@gmail.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Amruth Pillai
Cursor Agent
parent
b303b89758
commit
4ac19f81b3
@@ -29,6 +29,11 @@ const formSchema = pageSchema;
|
|||||||
|
|
||||||
type FormValues = z.infer<typeof formSchema>;
|
type FormValues = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
const CLAMP_MIN = 0;
|
||||||
|
const CLAMP_MAX = 100;
|
||||||
|
|
||||||
|
const clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max);
|
||||||
|
|
||||||
function PageSectionForm() {
|
function PageSectionForm() {
|
||||||
const resume = useResume();
|
const resume = useResume();
|
||||||
const page = resume?.data.metadata.page;
|
const page = resume?.data.metadata.page;
|
||||||
@@ -50,12 +55,17 @@ function PageSectionForm() {
|
|||||||
useSyncFormValues(form, page);
|
useSyncFormValues(form, page);
|
||||||
|
|
||||||
const handleAutoSave = <K extends keyof FormValues>(name: K, value: FormValues[K]) => {
|
const handleAutoSave = <K extends keyof FormValues>(name: K, value: FormValues[K]) => {
|
||||||
persist({ ...form.state.values, [name]: value });
|
const next = { ...form.state.values, [name]: value };
|
||||||
|
// Keep last-saved numeric page fields when the form holds a transient empty/NaN value
|
||||||
|
for (const key of ["marginX", "marginY", "gapX", "gapY"] as const) {
|
||||||
|
if (!Number.isFinite(next[key])) next[key] = page?.[key] ?? 0;
|
||||||
|
}
|
||||||
|
persist(next);
|
||||||
};
|
};
|
||||||
|
|
||||||
const pageNumberFields = [
|
const pageNumberFields = [
|
||||||
{ name: "marginX" as const, label: <Trans>Margin (Horizontal)</Trans>, min: 0, max: 100 as number | undefined },
|
{ name: "marginX" as const, label: <Trans>Margin (Horizontal)</Trans>, min: CLAMP_MIN, max: CLAMP_MAX },
|
||||||
{ name: "marginY" as const, label: <Trans>Margin (Vertical)</Trans>, min: 0, max: 100 as number | undefined },
|
{ name: "marginY" as const, label: <Trans>Margin (Vertical)</Trans>, min: CLAMP_MIN, max: CLAMP_MAX },
|
||||||
{ name: "gapX" as const, label: <Trans>Spacing (Horizontal)</Trans>, min: 0, max: undefined },
|
{ name: "gapX" as const, label: <Trans>Spacing (Horizontal)</Trans>, min: 0, max: undefined },
|
||||||
{ name: "gapY" as const, label: <Trans>Spacing (Vertical)</Trans>, min: 0, max: undefined },
|
{ name: "gapY" as const, label: <Trans>Spacing (Vertical)</Trans>, min: 0, max: undefined },
|
||||||
];
|
];
|
||||||
@@ -143,7 +153,7 @@ function PageSectionForm() {
|
|||||||
render={
|
render={
|
||||||
<InputGroupInput
|
<InputGroupInput
|
||||||
name={field.name}
|
name={field.name}
|
||||||
value={field.state.value}
|
value={Number.isFinite(field.state.value) ? field.state.value : ""}
|
||||||
min={min}
|
min={min}
|
||||||
{...(max !== undefined ? { max } : {})}
|
{...(max !== undefined ? { max } : {})}
|
||||||
step={1}
|
step={1}
|
||||||
@@ -151,7 +161,16 @@ function PageSectionForm() {
|
|||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const v = e.target.value;
|
const v = e.target.value;
|
||||||
const num = v === "" ? ("" as unknown as number) : Number(v);
|
if (v === "") {
|
||||||
|
// Allow clearing the controlled input without persisting invalid metadata
|
||||||
|
field.handleChange(Number.NaN);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const raw = Number(v);
|
||||||
|
if (!Number.isFinite(raw)) return;
|
||||||
|
|
||||||
|
const num = max !== undefined ? clamp(raw, min ?? 0, max) : Math.max(raw, min ?? 0);
|
||||||
field.handleChange(num);
|
field.handleChange(num);
|
||||||
handleAutoSave(name, num);
|
handleAutoSave(name, num);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -260,6 +260,19 @@ describe("pageSchema", () => {
|
|||||||
expect(pageSchema.safeParse(invalid).success).toBe(false);
|
expect(pageSchema.safeParse(invalid).success).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("falls back to default margins when out of range via .catch", () => {
|
||||||
|
const result = pageSchema.safeParse({
|
||||||
|
...defaultResumeData.metadata.page,
|
||||||
|
marginX: 1224,
|
||||||
|
marginY: 999,
|
||||||
|
});
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.data.marginX).toBe(14);
|
||||||
|
expect(result.data.marginY).toBe(12);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("falls back to 'a4' for unknown format via .catch", () => {
|
it("falls back to 'a4' for unknown format via .catch", () => {
|
||||||
const invalid = { ...defaultResumeData.metadata.page, format: "huge" };
|
const invalid = { ...defaultResumeData.metadata.page, format: "huge" };
|
||||||
const result = pageSchema.safeParse(invalid);
|
const result = pageSchema.safeParse(invalid);
|
||||||
|
|||||||
@@ -444,8 +444,8 @@ export const layoutSchema = z.object({
|
|||||||
export const pageSchema = z.object({
|
export const pageSchema = z.object({
|
||||||
gapX: z.number().min(0).describe("The horizontal gap between the sections of the page, defined in points (pt)."),
|
gapX: z.number().min(0).describe("The horizontal gap between the sections of the page, defined in points (pt)."),
|
||||||
gapY: z.number().min(0).describe("The vertical gap between the sections of the page, defined in points (pt)."),
|
gapY: z.number().min(0).describe("The vertical gap between the sections of the page, defined in points (pt)."),
|
||||||
marginX: z.number().min(0).describe("The horizontal margin of the page, defined in points (pt)."),
|
marginX: z.number().min(0).max(100).catch(14).describe("The horizontal margin of the page, defined in points (pt)."),
|
||||||
marginY: z.number().min(0).describe("The vertical margin of the page, defined in points (pt)."),
|
marginY: z.number().min(0).max(100).catch(12).describe("The vertical margin of the page, defined in points (pt)."),
|
||||||
format: z
|
format: z
|
||||||
.enum(["a4", "letter", "free-form"])
|
.enum(["a4", "letter", "free-form"])
|
||||||
.describe("The format of the page. Can be 'a4', 'letter', or 'free-form'.")
|
.describe("The format of the page. Can be 'a4', 'letter', or 'free-form'.")
|
||||||
|
|||||||
Reference in New Issue
Block a user