-
Notifications
You must be signed in to change notification settings - Fork 13
Add placeholder-color to bare-text-input #46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,10 @@ struct NativeUITextInputCore: View { | |
| let textSize: CGFloat | ||
| let contentColor: Color | ||
| let tintColor: Color | ||
| /// Bare-variant-only placeholder color override (`placeholder_color` / | ||
| /// `dark_placeholder_color`). `nil` for outlined/filled, which never | ||
| /// pass this — the placeholder then keeps the platform default gray. | ||
| var placeholderColor: Color? = nil | ||
|
|
||
| @State private var text: String = "" | ||
| @State private var lastSentValue: String = "" | ||
|
|
@@ -82,6 +86,13 @@ struct NativeUITextInputCore: View { | |
| fontSize: textSize, | ||
| fontName: fontName | ||
| ) | ||
| // `placeholder` doubles as the field's accessibility title in every | ||
| // init below; `prompt` (when non-nil) is what actually renders as | ||
| // the placeholder, letting us recolor it independently of the typed | ||
| // text without touching accessibility. `nil` falls back to the | ||
| // platform's default placeholder styling — unchanged from before | ||
| // this prop existed. | ||
| let styledPrompt: Text? = placeholderColor.map { Text(placeholder).foregroundStyle($0) } | ||
|
|
||
| // Apply `.foregroundColor` (not just `.foregroundStyle`) so the TYPED | ||
| // text adopts `contentColor`. SwiftUI's TextField/SecureField don't | ||
|
|
@@ -91,9 +102,14 @@ struct NativeUITextInputCore: View { | |
| if secure { | ||
| // SecureField has no selection binding — caret reporting is | ||
| // intentionally never available for secure fields. | ||
| SecureField(placeholder, text: $text) | ||
| SecureField(placeholder, text: $text, prompt: styledPrompt) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moving to With
<native:list>
<native:bare-text-input placeholder="Name" placeholder-color="slate-400" />
</native:list>can render "Name" twice — once as a leading label, once as the coloured placeholder. It's latent today and only appears once someone sets the new attribute, which makes it the kind of thing that gets reported as "placeholder-color broke my list" rather than traced back here.
|
||
| .foregroundColor(contentColor) | ||
| .focused($isFocused) | ||
| // `prompt` (when non-nil) turns `placeholder` into this | ||
| // field's label — visibly rendered by containers like | ||
| // `List`/`Form`. `.labelsHidden()` keeps it invisible | ||
| // (a no-op when `prompt == nil`, i.e. outlined/filled). | ||
| .labelsHidden() | ||
| } else if multiline { | ||
| // A vertical-axis TextField reports a ~0 intrinsic width when | ||
| // empty and won't expand to fill an ancestor's `maxWidth: | ||
|
|
@@ -108,27 +124,31 @@ struct NativeUITextInputCore: View { | |
| // (multiline) axis too. Kept as parallel branches so the | ||
| // feature-off path is byte-for-byte the original field. | ||
| if selectionEnabled { | ||
| TextField(placeholder, text: $text, selection: $selection, axis: .vertical) | ||
| TextField(placeholder, text: $text, selection: $selection, prompt: styledPrompt, axis: .vertical) | ||
| .lineLimit(lower...upper) | ||
| .foregroundColor(contentColor) | ||
| .frame(maxWidth: .infinity, alignment: .leading) | ||
| .focused($isFocused) | ||
| .labelsHidden() | ||
| } else { | ||
| TextField(placeholder, text: $text, axis: .vertical) | ||
| TextField(placeholder, text: $text, prompt: styledPrompt, axis: .vertical) | ||
| .lineLimit(lower...upper) | ||
| .foregroundColor(contentColor) | ||
| .frame(maxWidth: .infinity, alignment: .leading) | ||
| .focused($isFocused) | ||
| .labelsHidden() | ||
| } | ||
| } else { | ||
| if selectionEnabled { | ||
| TextField(placeholder, text: $text, selection: $selection) | ||
| TextField(placeholder, text: $text, selection: $selection, prompt: styledPrompt) | ||
| .foregroundColor(contentColor) | ||
| .focused($isFocused) | ||
| .labelsHidden() | ||
| } else { | ||
| TextField(placeholder, text: $text) | ||
| TextField(placeholder, text: $text, prompt: styledPrompt) | ||
| .foregroundColor(contentColor) | ||
| .focused($isFocused) | ||
| .labelsHidden() | ||
| } | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The explicit placeholder colour bypasses the disabled treatment.
displayedTextColoron the line above fades typed text toalpha = 0.6fwhenprops.disabled, and the two fallback branches here inherit that fade througheffectiveTextColor. But the two new override branches returnargbToComposeColor(...)at full strength, and Compose applies no view-level alpha to compensate.renders a full-strength placeholder on Android, while iOS fades the whole field via
.opacity(0.6)inNativeUIBareTextInputRenderer. The same markup reads as enabled on one platform and disabled on the other.Applying the same
.copy(alpha = 0.6f)to the resolved colour whenprops.disabledwould restore parity.