Skip to content

Conversation

@iamAbhi-916
Copy link
Contributor

@iamAbhi-916 iamAbhi-916 commented Dec 11, 2025

Description

Implements selectable prop for Text component for windows

Includes :

  • Basic text selection - Click and drag to select
  • Selection highlight - default windows blue accent background on selected text
  • Ctrl+C - Copy to clipboard
  • Ctrl+A - Select all text
  • Double-click - Select word
  • Right-click context menu - Copy / Select All options
  • Clear selection on click outside - Deselects when clicking elsewhere
  • Implements I-beam cursor for selectable text
  • Selection continues extending even when cursor is outside the selection range

Type of Change

  • New feature (non-breaking change which adds functionality)

Why

Parity with RN Android/IOS.

Resolves #13112

What

Made upstream changes to fix the issue where the selectable prop wasn’t being passed to native due to a macro conversion problem.
Ref: facebook/react-native#52599

Also updated logic in the Paragraph component view and composition event handler to correctly handle all selection-related scenarios, including text selection, pointer events, copy-to-clipboard, and other related behaviors.

Screenshots

text_selection.mp4

Testing

Tested in playground

Changelog

Should this change be included in the release notes: _indicate yes

Add a brief summary of the change to use in the release notes for the next release.
Adds text Component selection support for Fabric

Microsoft Reviewers: Open in CodeFlow

@iamAbhi-916 iamAbhi-916 changed the title Fabric : Implements selectable prop for Text component for windows Fabric : Implements selectable prop for text component for windows Dec 11, 2025
@vineethkuttan
Copy link
Contributor

Awesome work ! ,
Is the SelectionHighlightColor customizable through JavaScript?

@iamAbhi-916 iamAbhi-916 marked this pull request as ready for review December 12, 2025 05:29
@iamAbhi-916 iamAbhi-916 requested a review from a team as a code owner December 12, 2025 05:29
@iamAbhi-916
Copy link
Contributor Author

Awesome work ! , Is the SelectionHighlightColor customizable through JavaScript?

thank you!, for now default color is only supported (windows blue accent color) but will be taken up as soon this is merged(small change !).

Copy link
Contributor

@vineethkuttan vineethkuttan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@sundaramramaswamy
Copy link
Contributor

Please create a task to notify the app of selection change (JS).

@sundaramramaswamy
Copy link
Contributor

sundaramramaswamy commented Dec 12, 2025

I remember you talking about SHIFT + click selection. Please create a task for this too to avoid it slipping through the cracks.

@sundaramramaswamy
Copy link
Contributor

sundaramramaswamy commented Dec 12, 2025

Resolves [Add Relevant Issue Here]
#13112

Resolves #13112.

Drop template strings; they're noise in the actual PR message.

@sundaramramaswamy
Copy link
Contributor

sundaramramaswamy commented Dec 12, 2025

[Nit]

Implement selectable prop for <Text>

reads much nicer than

Implements selectable prop for Text component for windows

This is RNW so Windows is implicitly understood. Keep it minimal without data loss.

@sundaramramaswamy
Copy link
Contributor

sundaramramaswamy commented Dec 12, 2025

Please fix formatting issues in What and Changelog sections.

@iamAbhi-916 iamAbhi-916 changed the title Fabric : Implements selectable prop for text component for windows Fabric : Implements selectable prop for <Text> Dec 12, 2025
@sundaramramaswamy
Copy link
Contributor

sundaramramaswamy commented Dec 12, 2025

Just pointing out that this change is only about selecting text within a <Text> component and the user won't be able to select text spanning across two components, even if they're both selectable.

<View style={styles.selectionTestContainer}>
  <Text selectable={true} style={styles.sectionTitle}>Text Selection Test</Text>
  <Text selectable={true} style={styles.selectableText}>
    This text is SELECTABLE. Try clicking and dragging to select it.
  </Text>
</View>

@iamAbhi-916
Copy link
Contributor Author

iamAbhi-916 commented Dec 12, 2025

I know this may be beyond the scope of this PR but just pointing it out that this change is only about selecting text within a <Text> component and the user won't be able to select text spanning across two components.

<View style={styles.selectionTestContainer}>
  <Text selectable={true} style={styles.sectionTitle}>Text Selection Test</Text>
  <Text selectable={true} style={styles.selectableText}>
    This text is SELECTABLE. Try clicking and dragging to select it.
  </Text>
</View>

Good point!, this matches iOS and Android.

The React Native docs say selectable (ref: https://reactnative.dev/docs/text#selectable ) enables "native copy and paste functionality" - and native text views (UITextView on iOS, TextView on Android) don't support cross-view selection either. Each text view manages its own selection state independently.

@iamAbhi-916
Copy link
Contributor Author

iamAbhi-916 commented Dec 16, 2025

Please create a task to notify the app of selection change (JS).

tracking in ref: #15481

@iamAbhi-916
Copy link
Contributor Author

I remember you talking about SHIFT + click selection. Please create a task for this too to avoid it slipping through the cracks.

tracking in ref: #15481

Copy link
Contributor

@sundaramramaswamy sundaramramaswamy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Posted comments. Please address them.

Comment on lines +23 to +24
// Clears any active text selection in the application.
void ClearCurrentTextSelection() noexcept;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this different from void ClearSelection()? I see different comments b/w them:

// Clears any active text selection in the application.
void ClearCurrentTextSelection() noexcept;

// Called when losing focus, when another text starts selection, or when clicking outside text bounds.
void ClearSelection() noexcept;

However, for an implementation (leaf-node) method, when something is done doesn't matter. Only the doing matters.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that ClearCurrentTextSelection is a free-standing function, not a method of ParagraphComponentView. However, it should be a method of the parent entity which holds all the paragraph component view objects.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed offline, once you've a strong pointer to a ParagraphComponentView holding the current selection, just call ClearSelection on it. This ClearCurrentTextSelection can be dropped entirely.

Comment on lines +94 to +95
// Returns character position at the given point, or -1 if outside text bounds
int32_t getTextPositionAtPoint(facebook::react::Point pt) noexcept;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::optional<uint32_t> GetTextPositionAtPoint(facebook::react::Point pt) noexcept;

Consider switching to std::optional instead of using -1 (or other in-band signalling of error conditions) as it's easy to get it wrong. C++17 introduced this exclusively for cases like these. It'd be a must if this were a public method.

Interested to knowing more? Refer std::optional: How, when, and why -- devblogs.microsoft.com.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please switch to PascalCase.

float offsetY,
float pointScaleFactor) noexcept;
void updateTextAlignment(const std::optional<facebook::react::TextAlignment> &fbAlignment) noexcept;
bool isTextSelectableAtPoint(facebook::react::Point pt) noexcept;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please be consistent with method naming style. Except for upstream's functions, which use camelCase with override specified, most class functions follow PascalCase.

int32_t getTextPositionAtPoint(facebook::react::Point pt) noexcept;

// Returns the currently selected text, or empty string if no selection
std::string getSelectedText() const noexcept;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switch to PascalCase.

Comment on lines +415 to +417
int32_t selStart = std::min(m_selectionStart, m_selectionEnd);
int32_t selEnd = std::max(m_selectionStart, m_selectionEnd);
if (selStart < 0 || selEnd <= selStart || !m_textLayout) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Const correctness all throughout.

Comment on lines +241 to +242
float clampedX = std::max(0.0f, std::min(pt.x, textMetrics.width));
float clampedY = std::max(0.0f, std::min(pt.y, textMetrics.height));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Const correctness.

Comment on lines +428 to +429
renderTarget.GetDpi(&oldDpiX, &oldDpiY);
renderTarget.SetDpi(dpi, dpi);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is risky. Everywhere there's a function exit (normally or due to some error condition) you'd have to remember to reset old DPI on render target. Use a RAII wrapper, a unique_ptr for instance, which would undo this for you with no intervention of your's.

How? Examples with comments: 1 (only line 64 and below are relevant) and 2. Once you have such a type made, you can create an object and upon destruction it'd auto-revert to old DPI.

This is a very useful concept. Once you learn it you can avoid manual resource management for the most part using std::unique_ptr.

for (UINT32 i = 0; i < actualCount; i++) {
const auto &metric = hitTestMetrics[i];
D2D1_RECT_F rect = {metric.left, metric.top, metric.left + metric.width, metric.top + metric.height};
renderTarget.FillRectangle(&rect, selectionBrush.get());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ID2D1RenderTarget::FillRectangle already takes DIP (logical) why play with RT's DPI? We should be operating only in the logical coordinates space, no?

Refer Create a simple Direct2D application - learn.microsoft.com where FillRectangle is just in DIP and no mucking around with RT's DPI.

Comment on lines +696 to +697
int32_t selStart = std::min(m_selectionStart, m_selectionEnd);
int32_t selEnd = std::max(m_selectionStart, m_selectionEnd);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of checking before every usage, you can fix this when writing to these values to always have the lower limit to start and upper limit to end.

Please be mindful that such checks will fail in RTL text (Arabic, etc.). Doing it in one place, taking care of all these nuances, will rid this check off other places. They can blindly read these values without worrying about constructing meaning out of them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement selectable property for Text for fabric

4 participants