Conversation
Adds a `content-type` attribute (also `contentType`, and HTML's `autocomplete`) to every text input variant. It maps to `textContentType` on iOS and the Compose autofill `contentType` on Android: username, email, password (current-password), new-password and one-time-code. Without it iOS has no declared credential pair on a login form, and a secure field filled as the partner of another field can be written in UIKit without its `@change` ever firing (NativePHP/mobile-air#422). Nothing is inferred when the attribute is unset, so existing fields behave exactly as before on both platforms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes NativePHP/mobile-air#422 once merged. It lives here rather than in mobile-air because the text input renderer is a plugin component, so the cross-repo reference won't close the issue automatically; it will need closing by hand.
#422 reported two things, and with this PR both are dealt with:
@change. This no longer reproduces on stock 0.6.0 (verified on an iPhone; see Testing), so it was fixed somewhere between 0.4.0 and 0.6.0.This is a feature, not the fix for #422. The bug #422 reports (a secure field filled by AutoFill as the partner of another field never reaching
@change) no longer reproduces on stock 0.6.0; see Testing. What #422 also asked for, and what nothing in mobile-ui offers yet, is a way to tell the platform what a field holds. That's this PR.What it adds
A
content-typeattribute on every text input variant (outlined-text-input,filled-text-input,bare-text-input), also accepted ascontentTypeand as HTML'sautocomplete, plus a fluent->contentType().textContentTypeContentTypeusername.usernameUsernameemail.emailAddressEmailAddresspassword(aliascurrent-password).passwordPasswordnew-password.newPasswordNewPasswordone-time-code.oneTimeCodeSmsOtpCodeValues are HTML
autocompletetokens. camelCase and snake_case are normalized (newPassword,new_password→new-password), andemail-addressis an alias foremail. Unknown values pass through and are ignored natively, the same policy askeyboardandautocapitalize.Why
Neither renderer sets a content type, so there's no way to tell the OS which field is which:
new-password;one-time-code.Unset means unchanged
Nothing is inferred when the attribute is absent, so every existing field behaves exactly as it did.
.textContentType(nil)because SwiftUI may assign its own type to aSecureField, and an explicit nil could change existing password fields.Modifier.semantics { contentType = … }is only added when set, so Compose's own keyboard-derived type is untouched otherwise.Android notes, from the Compose 1.10.0 source (BOM
2025.12.00)CoreTextFieldSemanticsModifierhandlesonFillDataand routes a fill through the normal value-change path, so it reachesonValueChange.EmailAddress, Password →Password, Phone →PhoneNumber). The author's modifier sits at the head of the chain, and semantics are applied tail to head, so the author's value is written last.keyboard="email"withcontent-type="username"therefore works.securealone gets no derived type. Masking is a visual transformation, not a keyboard type, so on Android this prop is the only way to declare such a field a password.Not in this PR
Testing
tests/BaseTextInputContentTypeTest.php: 27 tests covering all three variants and all three attribute spellings, normalization, aliases, omission when unset or empty, pass-through of unknown values, and coexistence withkeyboardandsecure.@changeand signed in, and a one-time code filled aone-time-codefield.content-type, also signed in. So #422's partner-fill bug is already gone in 0.6.0, and this PR doesn't fix it.usernameandpassword, Google autofill returned one dataset covering both fields and filled them as a pair, and tracked the pair for saving.one-time-codeinput was offered to the service as fillable.