From 9036e71516736deea0364b72fbbee5a65de5a116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 15 May 2026 18:26:46 +0200 Subject: [PATCH] feat(ui): derive Input inputMode default from the type prop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Surface the right on-screen keyboard on iOS Safari and Android Chrome without forcing every call site to repeat the attribute. The primitive maps `type="number"` to `inputMode="decimal"` (most numeric fields in HealthLog accept decimals — kg, mmol/L, sleep hours), `type="email"` to `"email"`, `type="url"` to `"url"`, `type="tel"` to `"tel"`, and `type="search"` to `"search"`. Integer-only call sites (steps, doses per pen, reminder minutes) still opt into `inputMode="numeric"` explicitly and the override wins. Cover the derivation with seven additional unit tests on top of the three existing autoComplete contracts; the suite now pins every mapping plus the override path. --- src/components/ui/__tests__/input.test.tsx | 40 ++++++++++++++++++++++ src/components/ui/input.tsx | 30 ++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/src/components/ui/__tests__/input.test.tsx b/src/components/ui/__tests__/input.test.tsx index a5b7197a5..fe23398d5 100644 --- a/src/components/ui/__tests__/input.test.tsx +++ b/src/components/ui/__tests__/input.test.tsx @@ -28,4 +28,44 @@ describe("", () => { expect(html).not.toContain("lpignore"); expect(html).not.toContain("1p-ignore"); }); + + it("derives inputMode=decimal from type=number", () => { + const html = renderToStaticMarkup(); + expect(html).toContain('inputMode="decimal"'); + }); + + it("derives inputMode=email from type=email", () => { + const html = renderToStaticMarkup( + , + ); + expect(html).toContain('inputMode="email"'); + }); + + it("derives inputMode=url from type=url", () => { + const html = renderToStaticMarkup(); + expect(html).toContain('inputMode="url"'); + }); + + it("derives inputMode=tel from type=tel", () => { + const html = renderToStaticMarkup(); + expect(html).toContain('inputMode="tel"'); + }); + + it("derives inputMode=search from type=search", () => { + const html = renderToStaticMarkup(); + expect(html).toContain('inputMode="search"'); + }); + + it("omits inputMode for plain text inputs", () => { + const html = renderToStaticMarkup(); + expect(html).not.toContain("inputMode="); + }); + + it("respects an explicit inputMode override on type=number", () => { + const html = renderToStaticMarkup( + , + ); + expect(html).toContain('inputMode="numeric"'); + expect(html).not.toContain('inputMode="decimal"'); + }); }); diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx index c993f5e1b..00cb641a5 100644 --- a/src/components/ui/input.tsx +++ b/src/components/ui/input.tsx @@ -19,21 +19,51 @@ import { cn } from "@/lib/utils"; * `autoComplete` is anything other than `"off"`, the LastPass / * 1Password ignore attrs are dropped so password managers fill the * field normally. + * + * v1.4.27 mobile pass: derive a sensible `inputMode` default from the + * `type` prop so every numeric / email / tel / url / search field + * surfaces the right on-screen keyboard on iOS Safari and Android + * Chrome without each call site having to repeat the attribute. Most + * `type="number"` fields in HealthLog accept decimals (kg, mmol/L, + * sleep hours), so the default is `"decimal"`; integer-only call + * sites can still pass `inputMode="numeric"` explicitly. */ +function deriveInputMode( + type: React.HTMLInputTypeAttribute | undefined, +): React.HTMLAttributes["inputMode"] | undefined { + switch (type) { + case "number": + return "decimal"; + case "tel": + return "tel"; + case "email": + return "email"; + case "url": + return "url"; + case "search": + return "search"; + default: + return undefined; + } +} + function Input({ className, type, autoComplete, + inputMode, ...props }: React.ComponentProps<"input">) { const resolvedAutoComplete = autoComplete ?? "off"; const skipAutofill = resolvedAutoComplete === "off"; + const resolvedInputMode = inputMode ?? deriveInputMode(type); return (