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 (