Uh oh!
There was an error while loading. Please reload this page.
[iOS/macOS] [TextInput] Implement ghost text - #1890
Conversation
Ghost text is hint text that is inserted inline as a hint to the user (for example predictive text). Unlike autocomplete for combo boxes, which is "visible" to the model, ghost text is "invisible" or transparent to the model (i.e. not observable in any callback or imperative method). Ghost text isn't selectable by the user. Ideally we do ghost text only in the display layer, meaning it isn't in the model. Unfortunately it doesn't look like we can render ghost text and still have Apple flow text for us (ghost text that is in the middle of regular text will want to reflow the regular text to make room for ghost text). Overriding all text box rendering seems like substantial rework (we need to deal with all drawing, including but not limited to the text box background, text, insertion pointer\selection, ... as well as other ancillary things around positioning related input UI such as IME's), so here we DO introduce it in the model, but try to keep awareness on the native side, and away from Application logic. Since ghost text is presumed dependent on immediate user context (e.g. text and selection), we reset ghost text automatically when either of these change. We could *not* do that, but that requires us to deal with any possible edits around\on the ghost text, as well as actually ensure we "hide" ghost text from any callbacks made to the app (something we are getting away with right now since we remove ghost text on pretty much any user interaction). Plumbing for new imperative method to set ghost text. * Libraries/Components/TextInput/RCTMultilineTextInputNativeComponent.js * Libraries/Components/TextInput/RCTSingelineTextInputNativeComponent.js * Libraries/Components/TextInput/TextInput.flow.js * Libraries/Components/TextInput/TextInput.js * Libraries/Components/TextInput/TextInputNativeCommands.js * Libraries/Text/TextInput/RCTBaseTextInputViewManager.m Introduce a property on the `RCTUITextView` and `RCTUITextField` to store state on whether we're currently in the midst of setting\clearing ghost text. We use this to suppress various callbacks to the Application that may be triggered as a result of us changing the ghost text\selection to compensate for addition\removal of ghost text. * Libraries/Text/TextInput/Multiline/RCTUITextView.h * Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.m * Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h * Libraries/Text/TextInput/Singleline/RCTUITextField.h Actual ghost text management logic. I placed as much as I reasonably could here since that allows us to share most of the functionality between single and multi line text inputs. * Libraries/Text/TextInput/RCTBaseTextInputView.h * Libraries/Text/TextInput/RCTBaseTextInputView.m Reintroduce `onTextInput` into Flow typing. This was removed in commit 3f7e0a2, but it looks like it wasn't ever removed on the native side, so the callback still "works" for now. I am using this callback to determine ghost text behavior correctness, and since it is fired, I need it back. When we truly remove this from the native side, we can stop caring about it. * Libraries/Components/TextInput/TextInput.flow.js * Libraries/Components/TextInput/TextInput.js Test page for ghost text. Shows predictive text example. * packages/rn-tester/js/examples/GhostText/GhostText.js * packages/rn-tester/js/utils/RNTesterList.ios.js Testing (macOS\iOS x multi\single line): * Type predictable text (see test page for list), see that we get ghost text and that isn't observable in the callbacks * Type non-predictable text, check ordering of events consistent with predictable text case * Log calls to `setGhostText:` on native. Ensure we clear ghost text (due to text chagnes) * Change selection with ghost text, check that we remove ghost text (due to selection change) * Hit Esc in text box with ghost text, check that we remove ghost text (due to focus loss)
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Saad Najmi (Saadnajmi)
left a comment
There was a problem hiding this comment.
I guess now that we aren't using onTextInput, we don't need to flow type added, but I wouldn't call that blocking...
Navneet Kambo (nakambo)
commented
Aug 2, 2023
I'm not adding that anymore -- the types are still being updated for |
Navneet Kambo (nakambo)
commented
Aug 2, 2023
Oops, accidentally closed the PR |
…tion fixes (#2912) ## Summary TextInput commands (`setGhostText`, `blur`, `setSelection`) don't work correctly under Fabric on macOS. This PR fixes three issues: - **Add missing `setGhostText` command to Fabric**: Ghost text was implemented for Paper in #1890 / #1897 (with undo fixes in #2105 / #2106), but the Fabric command dispatcher and component view never got the implementation. This ports the ghost text logic (insertion, removal, attributes, delegate cleanup) from `RCTBaseTextInputView` to `RCTTextInputComponentView`. - **Fix `blur` command on macOS Fabric**: `blur` was a silent no-op because it compared `[window firstResponder]` against the NSTextField, but on macOS the firstResponder during editing is the field editor (NSTextView), not the text field itself. Fixed by checking `currentEditor` on NSTextField. - **Fix ghost text being selectable on Fabric**: Ghost text could be selected by the user on Fabric (but not Paper). Added cleanup in `textInputDidChangeSelection` to clear ghost text when the user changes selection, matching Paper behavior. - **Fix `setTextAndSelection` delegate notification**: Changed `notifyDelegate:YES` to `notifyDelegate:NO` to match Paper's `setSelectionStart:selectionEnd:` behavior and prevent spurious delegate callbacks during programmatic selection changes. ## Test plan - [x] Verified `setGhostText` command works via RNTester on macOS Fabric - [x] Verified `blur` command works on macOS Fabric - [x] Verified ghost text is not selectable on Fabric - [x] Verified `focus`, `clear`, `setSelection` commands still work - [x] JS TextInput tests pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…ion fixes (#2937) ## Summary Backport of #2912 to 0.81-stable. - **Add missing `setGhostText` command to Fabric**: Ghost text was implemented for Paper in #1890 / #1897 (with undo fixes in #2105 / #2106), but the Fabric command dispatcher and component view never got the implementation. This ports the ghost text logic (insertion, removal, attributes, delegate cleanup) from `RCTBaseTextInputView` to `RCTTextInputComponentView`. - **Fix `blur` command on macOS Fabric**: `blur` was a silent no-op because it compared `[window firstResponder]` against the NSTextField, but on macOS the firstResponder during editing is the field editor (NSTextView), not the text field itself. Fixed by checking `currentEditor` on NSTextField. - **Fix ghost text being selectable on Fabric**: Ghost text could be selected by the user on Fabric (but not Paper). Added cleanup in `textInputDidChangeSelection` to clear ghost text when the user changes selection, matching Paper behavior. - **Fix `setTextAndSelection` delegate notification**: Changed `notifyDelegate:YES` to `notifyDelegate:NO` to match Paper's `setSelectionStart:selectionEnd:` behavior and prevent spurious delegate callbacks during programmatic selection changes. ## Test plan - [x] Verified `setGhostText` command works via RNTester on macOS Fabric - [x] Verified `blur` command works on macOS Fabric - [x] Verified ghost text is not selectable on Fabric - [x] Verified `focus`, `clear`, `setSelection` commands still work - [x] JS TextInput tests pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Please select one of the following
Summary
Ghost text is hint text that is inserted inline as a hint to the user (for example predictive text). Unlike autocomplete for combo boxes, which is "visible" to the model, ghost text is "invisible" or transparent to the model (i.e. not observable in any callback or imperative method). Ghost text isn't selectable by the user.
Ideally we do ghost text only in the display layer, meaning it isn't in the model. Unfortunately it doesn't look like we can render ghost text and still have Apple flow text for us (ghost text that is in the middle of regular text will want to reflow the regular text to make room for ghost text). Overriding all text box rendering seems like substantial rework (we need to deal with all drawing, including but not limited to the text box background, text, insertion pointer\selection, ... as well as other ancillary things around positioning related input UI such as IME's), so here we DO introduce it in the model, but try to keep awareness on the native side, and away from Application logic.
Since ghost text is presumed dependent on immediate user context (e.g. text and selection), we reset ghost text automatically when either of these change. We could not do that, but that requires us to deal with any possible edits around\on the ghost text, as well as actually ensure we "hide" ghost text from any callbacks made to the app (something we are getting away with right now since we remove ghost text on pretty much any user interaction).
Plumbing for new imperative method to set ghost text.
Introduce a property on the
RCTUITextViewandRCTUITextFieldto store state on whether we're currently in the midst of setting\clearing ghost text. We use this to suppress various callbacks to the Application that may be triggered as a result of us changing the ghost text\selection to compensate for addition\removal of ghost text.Actual ghost text management logic. I placed as much as I reasonably could here since that allows us to share most of the functionality between single and multi line text inputs.
Test page for ghost text. Shows predictive text example.
Changelog
GENERAL CHANGED - Initial commit
Test Plan
Testing (macOS\iOS x multi\single line):
setGhostText:on native. Ensure we clear ghost text (due to text chagnes)Test page example video: https://github.com/microsoft/react-native-macos/assets/72474613/38f466c3-695b-4526-bbb2-a5afb8e333d8