Skip to content

Commit 1d7bc89

Browse files
authored
[0.82] Enables word-extension during drag after a double-click selection (#15640)
* enables word-extension during drag after a double-click selection * yarn lint:fix and format * Change files
1 parent 2592b3f commit 1d7bc89

3 files changed

Lines changed: 59 additions & 8 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "enables word-extension during drag after a double-click selection",
4+
"packageName": "react-native-windows",
5+
"email": "74712637+iamAbhi-916@users.noreply.github.com",
6+
"dependentChangeType": "patch"
7+
}

vnext/Microsoft.ReactNative/Fabric/Composition/ParagraphComponentView.cpp

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,7 @@ void ParagraphComponentView::ClearSelection() noexcept {
548548
m_selectionStart = std::nullopt;
549549
m_selectionEnd = std::nullopt;
550550
m_isSelecting = false;
551+
m_isWordSelecting = false;
551552
if (hadSelection) {
552553
// Clears selection highlight
553554
DrawText();
@@ -598,7 +599,13 @@ void ParagraphComponentView::OnPointerPressed(
598599

599600
if (isDoubleClick) {
600601
SelectWordAtPosition(*charPosition);
601-
m_isSelecting = false;
602+
if (m_selectionStart && m_selectionEnd) {
603+
m_isWordSelecting = true;
604+
m_wordAnchorStart = *m_selectionStart;
605+
m_wordAnchorEnd = *m_selectionEnd;
606+
m_isSelecting = true;
607+
CapturePointer(args.Pointer());
608+
}
602609
} else {
603610
// Single-click: start drag selection
604611
m_selectionStart = charPosition;
@@ -640,10 +647,28 @@ void ParagraphComponentView::OnPointerMoved(
640647
facebook::react::Point localPt{position.X, position.Y};
641648
std::optional<int32_t> charPosition = GetClampedTextPosition(localPt);
642649

643-
if (charPosition && charPosition != m_selectionEnd) {
644-
m_selectionEnd = charPosition;
645-
DrawText();
646-
args.Handled(true);
650+
if (charPosition) {
651+
if (m_isWordSelecting) {
652+
// Extend selection by whole words
653+
auto [wordStart, wordEnd] = GetWordBoundariesAtPosition(*charPosition);
654+
655+
if (*charPosition < m_wordAnchorStart) {
656+
m_selectionStart = wordStart;
657+
m_selectionEnd = m_wordAnchorEnd;
658+
} else if (*charPosition >= m_wordAnchorEnd) {
659+
m_selectionStart = m_wordAnchorStart;
660+
m_selectionEnd = wordEnd;
661+
} else {
662+
m_selectionStart = m_wordAnchorStart;
663+
m_selectionEnd = m_wordAnchorEnd;
664+
}
665+
DrawText();
666+
args.Handled(true);
667+
} else if (charPosition != m_selectionEnd) {
668+
m_selectionEnd = charPosition;
669+
DrawText();
670+
args.Handled(true);
671+
}
647672
}
648673
}
649674

@@ -667,6 +692,7 @@ void ParagraphComponentView::OnPointerReleased(
667692
}
668693

669694
m_isSelecting = false;
695+
m_isWordSelecting = false;
670696

671697
ReleasePointerCapture(args.Pointer());
672698

@@ -691,6 +717,7 @@ void ParagraphComponentView::OnPointerCaptureLost() noexcept {
691717
// Pointer capture was lost stop any active selection drag
692718
if (m_isSelecting) {
693719
m_isSelecting = false;
720+
m_isWordSelecting = false;
694721

695722
if (!m_selectionStart || !m_selectionEnd || *m_selectionStart == *m_selectionEnd) {
696723
m_selectionStart = std::nullopt;
@@ -741,12 +768,17 @@ void ParagraphComponentView::CopySelectionToClipboard() noexcept {
741768
winrt::Windows::ApplicationModel::DataTransfer::Clipboard::SetContent(dataPackage);
742769
}
743770

744-
void ParagraphComponentView::SelectWordAtPosition(int32_t charPosition) noexcept {
771+
std::pair<int32_t, int32_t> ParagraphComponentView::GetWordBoundariesAtPosition(int32_t charPosition) noexcept {
745772
const std::wstring utf16Text{facebook::react::WindowsTextLayoutManager::GetTransformedText(m_attributedStringBox)};
746773
const int32_t textLength = static_cast<int32_t>(utf16Text.length());
747774

748-
if (utf16Text.empty() || charPosition < 0 || charPosition >= textLength) {
749-
return;
775+
if (utf16Text.empty() || charPosition < 0) {
776+
return {0, 0};
777+
}
778+
779+
charPosition = std::min(charPosition, textLength - 1);
780+
if (charPosition < 0) {
781+
return {0, 0};
750782
}
751783

752784
int32_t wordStart = charPosition;
@@ -779,6 +811,12 @@ void ParagraphComponentView::SelectWordAtPosition(int32_t charPosition) noexcept
779811
}
780812
}
781813

814+
return {wordStart, wordEnd};
815+
}
816+
817+
void ParagraphComponentView::SelectWordAtPosition(int32_t charPosition) noexcept {
818+
auto [wordStart, wordEnd] = GetWordBoundariesAtPosition(charPosition);
819+
782820
if (wordEnd > wordStart) {
783821
SetSelection(wordStart, wordEnd);
784822
DrawText();

vnext/Microsoft.ReactNative/Fabric/Composition/ParagraphComponentView.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ struct ParagraphComponentView : ParagraphComponentViewT<ParagraphComponentView,
9898
void CopySelectionToClipboard() noexcept;
9999

100100
void SelectWordAtPosition(int32_t charPosition) noexcept;
101+
std::pair<int32_t, int32_t> GetWordBoundariesAtPosition(int32_t charPosition) noexcept;
101102

102103
void ShowContextMenu() noexcept;
103104

@@ -114,6 +115,11 @@ struct ParagraphComponentView : ParagraphComponentViewT<ParagraphComponentView,
114115
std::optional<int32_t> m_selectionEnd;
115116
bool m_isSelecting{false};
116117

118+
// Double click + drag selection
119+
bool m_isWordSelecting{false};
120+
int32_t m_wordAnchorStart{0};
121+
int32_t m_wordAnchorEnd{0};
122+
117123
// Double-click detection
118124
std::chrono::steady_clock::time_point m_lastClickTime{};
119125
std::optional<int32_t> m_lastClickPosition;

0 commit comments

Comments
 (0)