Skip to content
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

Add cursorColor support to TextInput #11502

Merged
merged 2 commits into from
Apr 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Add cursorColor support to TextInput",
"packageName": "react-native-windows",
"email": "[email protected]",
"dependentChangeType": "patch"
}
200 changes: 125 additions & 75 deletions packages/playground/Samples/textinput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
Keyboard,
View,
KeyboardAvoidingView,
ScrollView,
} from 'react-native';

import type {EventSubscription} from 'react-native/Libraries/vendor/emitter/EventEmitter';
Expand Down Expand Up @@ -58,84 +59,133 @@ export default class Bootstrap extends React.Component<{}, any> {
render() {
let textInputRef: TextInput | null;
return (
<View style={styles.container}>
<TextInput
style={styles.input}
placeholder={'MultiLine'}
multiline={true}
/>
<TextInput
style={styles.input}
placeholder={'ReadOnly'}
editable={false}
/>
<TextInput
style={styles.input}
placeholder={'SpellChecking Disabled'}
spellCheck={false}
/>
<TextInput
style={styles.input}
placeholder={'PlaceHolder color blue'}
placeholderTextColor="blue"
/>
<TextInput
style={styles.input}
placeholder={'contextMenuHidden'}
contextMenuHidden={true}
/>
<TextInput
style={styles.input}
caretHidden={true}
placeholder={'caretHidden'}
/>
<TextInput
style={styles.input}
keyboardType="number-pad"
placeholder={'number-pad keyboardType'}
/>
<TextInput
style={styles.input}
autoCapitalize="characters"
placeholder={'autoCapitalize characters'}
/>
<TextInput
ref={ref => (textInputRef = ref)}
onFocus={() => setTimeout(() => textInputRef?.blur(), 5000)}
placeholder={'blurs after 5 seconds'}
style={styles.input}
/>
<TextInput
style={styles.input}
placeholder={this.state.passwordHidden ? 'Password' : 'Text'}
autoCapitalize="none"
secureTextEntry={this.state.passwordHidden}
onChangeText={text => {
this.setState({text});
}}
value={this.state.text}
selectionColor="red"
maxLength={10}
keyboardType="numeric"
/>
<Button
title={
this.state.passwordHidden
? 'SecureTextEntry On'
: 'SecureTextEntry Off'
}
onPress={this.onPressShowPassword}
/>
<KeyboardAvoidingView
style={styles.container}
behavior="padding"
enabled>
<ScrollView>
<View style={styles.container}>
<TextInput
style={styles.input}
placeholder={'KeyboardAvoidingView padding'}
placeholder={'MultiLine'}
multiline={true}
/>
</KeyboardAvoidingView>
</View>
<TextInput
style={styles.input}
placeholder={'ReadOnly'}
editable={false}
/>
<TextInput
style={styles.input}
placeholder={'SpellChecking Disabled'}
spellCheck={false}
/>
<TextInput
style={styles.input}
placeholder={'PlaceHolder color blue'}
placeholderTextColor="blue"
/>
<TextInput
style={styles.input}
placeholder={'contextMenuHidden'}
contextMenuHidden={true}
/>
<TextInput
style={styles.input}
caretHidden={true}
placeholder={'caretHidden'}
/>
<TextInput
style={styles.input}
keyboardType="number-pad"
placeholder={'number-pad keyboardType'}
/>
<TextInput
style={styles.input}
autoCapitalize="characters"
placeholder={'autoCapitalize characters'}
/>
<TextInput
ref={ref => (textInputRef = ref)}
onFocus={() => setTimeout(() => textInputRef?.blur(), 5000)}
placeholder={'blurs after 5 seconds'}
style={styles.input}
/>
<TextInput
style={styles.input}
placeholder={this.state.passwordHidden ? 'Password' : 'Text'}
autoCapitalize="none"
secureTextEntry={this.state.passwordHidden}
onChangeText={text => {
this.setState({text});
}}
value={this.state.text}
selectionColor="red"
maxLength={10}
keyboardType="numeric"
/>
<Button
title={
this.state.passwordHidden
? 'SecureTextEntry On'
: 'SecureTextEntry Off'
}
onPress={this.onPressShowPassword}
/>
<TextInput
placeholder="Single line"
cursorColor="#00FF00"
placeholderTextColor="grey"
style={[
styles.input,
{backgroundColor: 'black', color: 'white', marginBottom: 4},
]}
/>
<TextInput
placeholder="Single line with caret color and caret hidden"
cursorColor="#00FF00"
caretHidden={true}
placeholderTextColor="grey"
style={[
styles.input,
{backgroundColor: 'black', color: 'white', marginBottom: 4},
]}
/>
<TextInput
multiline={true}
placeholder="Multiline"
cursorColor="#00FF00"
placeholderTextColor="grey"
style={[
styles.input,
{backgroundColor: 'black', color: 'white', marginBottom: 4},
]}
/>
<TextInput
placeholder="Single line with selection color"
cursorColor="#00FF00"
selectionColor="yellow"
placeholderTextColor="grey"
style={[
styles.input,
{backgroundColor: 'black', color: 'white', marginBottom: 4},
]}
/>
<TextInput
multiline={true}
placeholder="Multiline with selection color"
cursorColor="#00FF00"
selectionColor="yellow"
placeholderTextColor="grey"
style={[styles.input, {backgroundColor: 'black', color: 'white'}]}
/>
<KeyboardAvoidingView
style={styles.container}
behavior="padding"
enabled>
<TextInput
style={styles.input}
placeholder={'KeyboardAvoidingView padding'}
/>
</KeyboardAvoidingView>
</View>
</ScrollView>
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ WindowsTextInputProps::WindowsTextInputProps(
placeholderTextColor(
convertRawProp(context, rawProps, "placeholderTextColor", sourceProps.placeholderTextColor, {})),
scrollEnabled(convertRawProp(context, rawProps, "scrollEnabled", sourceProps.scrollEnabled, {true})),
cursorColor(convertRawProp(context, rawProps, "cursorColor", sourceProps.cursorColor, {})),
selection(convertRawProp(context, rawProps, "selection", sourceProps.selection, {})),
selectionColor(convertRawProp(context, rawProps, "selectionColor", sourceProps.selectionColor, {})),
selectTextOnFocus(convertRawProp(context, rawProps, "selectTextOnFocus", sourceProps.selectTextOnFocus, {false})),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ class WindowsTextInputProps final : public ViewProps, public BaseTextProps {
std::string placeholder{};
SharedColor placeholderTextColor{};
bool scrollEnabled{true};
SharedColor cursorColor{};
CompWindowsTextInputSelectionStruct selection{};
SharedColor selectionColor{};
bool selectTextOnFocus{false};
Expand Down
34 changes: 26 additions & 8 deletions vnext/Microsoft.ReactNative/Views/TextInputViewManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class TextInputShadowNode : public ShadowNodeBase {
void dispatchTextInputChangeEvent(winrt::hstring newText);
void registerEvents();
void registerPreviewKeyDown();
void HideCaretIfNeeded();
void UpdateCaretColorOrHideIfNeeded();
void setPasswordBoxPlaceholderForeground(
xaml::Controls::PasswordBox passwordBox,
const winrt::Microsoft::ReactNative::JSValue &color);
Expand All @@ -144,6 +144,8 @@ class TextInputShadowNode : public ShadowNodeBase {
bool m_hideCaret = false;
bool m_shouldClearTextOnSubmit = false;

winrt::Microsoft::ReactNative::JSValue m_cursorColor;

winrt::Microsoft::ReactNative::JSValue m_placeholderTextColor;
std::vector<HandledKeyboardEvent> m_submitKeyEvents{};

Expand Down Expand Up @@ -260,7 +262,7 @@ void TextInputShadowNode::registerEvents() {
control.as<xaml::Controls::PasswordBox>().SelectAll();
}
}
HideCaretIfNeeded();
UpdateCaretColorOrHideIfNeeded();

folly::dynamic eventData = folly::dynamic::object("target", tag);
if (!m_updating)
Expand Down Expand Up @@ -330,7 +332,7 @@ void TextInputShadowNode::registerEvents() {
}
});
}
HideCaretIfNeeded();
UpdateCaretColorOrHideIfNeeded();
});

if (control.try_as<xaml::IUIElement7>()) {
Expand Down Expand Up @@ -448,14 +450,24 @@ bool TextInputShadowNode::IsTextBox() {
return !!GetView().try_as<xaml::Controls::TextBox>();
}

// hacking solution to hide the caret
void TextInputShadowNode::HideCaretIfNeeded() {
// hacking solution to hide the caret or change its color
void TextInputShadowNode::UpdateCaretColorOrHideIfNeeded() {
bool updateRequired = false;
xaml::Media::SolidColorBrush color;

if (m_hideCaret) {
updateRequired = true;
color = xaml::Media::SolidColorBrush(winrt::Colors::Transparent());
} else if (!m_cursorColor.IsNull()) {
updateRequired = true;
color = SolidColorBrushFrom(m_cursorColor);
}

if (updateRequired) {
auto control = GetView().as<xaml::Controls::Control>();
if (auto caret = FindCaret(control)) {
caret.CompositeMode(xaml::Media::ElementCompositeMode::Inherit);
xaml::Media::SolidColorBrush transparentColor(winrt::Colors::Transparent());
caret.Fill(transparentColor);
caret.Fill(color);
}
}
}
Expand Down Expand Up @@ -520,7 +532,7 @@ void TextInputShadowNode::updateProperties(winrt::Microsoft::ReactNative::JSValu
} else if (propertyName == "caretHidden") {
if (propertyValue.Type() == winrt::Microsoft::ReactNative::JSValueType::Boolean) {
m_hideCaret = propertyValue.AsBoolean();
HideCaretIfNeeded();
UpdateCaretColorOrHideIfNeeded();
}
} else if (propertyName == "focusable") {
// parent class also sets isTabStop
Expand Down Expand Up @@ -581,6 +593,11 @@ void TextInputShadowNode::updateProperties(winrt::Microsoft::ReactNative::JSValu
isTextBox ? xaml::Controls::TextBox::PlaceholderTextProperty()
: xaml::Controls::PasswordBox::PlaceholderTextProperty());
}
} else if (propertyName == "cursorColor") {
m_cursorColor = nullptr;
if (IsValidColorValue(propertyValue)) {
m_cursorColor = propertyValue.Copy();
}
} else if (propertyName == "selectionColor") {
if (IsValidColorValue(propertyValue)) {
control.SetValue(
Expand Down Expand Up @@ -822,6 +839,7 @@ void TextInputViewManager::GetNativeProps(const winrt::Microsoft::ReactNative::I
React::WriteProperty(writer, L"placeholder", L"string");
React::WriteProperty(writer, L"placeholderTextColor", L"Color");
React::WriteProperty(writer, L"scrollEnabled", L"boolean");
React::WriteProperty(writer, L"cursorColor", L"Color");
React::WriteProperty(writer, L"selection", L"Map");
React::WriteProperty(writer, L"selectionColor", L"Color");
React::WriteProperty(writer, L"selectTextOnFocus", L"boolean");
Expand Down