Skip to content

Commit 5cff7ee

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

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
@@ -539,6 +539,7 @@ void ParagraphComponentView::ClearSelection() noexcept {
539539
m_selectionStart = std::nullopt;
540540
m_selectionEnd = std::nullopt;
541541
m_isSelecting = false;
542+
m_isWordSelecting = false;
542543
if (hadSelection) {
543544
// Clears selection highlight
544545
DrawText();
@@ -589,7 +590,13 @@ void ParagraphComponentView::OnPointerPressed(
589590

590591
if (isDoubleClick) {
591592
SelectWordAtPosition(*charPosition);
592-
m_isSelecting = false;
593+
if (m_selectionStart && m_selectionEnd) {
594+
m_isWordSelecting = true;
595+
m_wordAnchorStart = *m_selectionStart;
596+
m_wordAnchorEnd = *m_selectionEnd;
597+
m_isSelecting = true;
598+
CapturePointer(args.Pointer());
599+
}
593600
} else {
594601
// Single-click: start drag selection
595602
m_selectionStart = charPosition;
@@ -631,10 +638,28 @@ void ParagraphComponentView::OnPointerMoved(
631638
facebook::react::Point localPt{position.X, position.Y};
632639
std::optional<int32_t> charPosition = GetClampedTextPosition(localPt);
633640

634-
if (charPosition && charPosition != m_selectionEnd) {
635-
m_selectionEnd = charPosition;
636-
DrawText();
637-
args.Handled(true);
641+
if (charPosition) {
642+
if (m_isWordSelecting) {
643+
// Extend selection by whole words
644+
auto [wordStart, wordEnd] = GetWordBoundariesAtPosition(*charPosition);
645+
646+
if (*charPosition < m_wordAnchorStart) {
647+
m_selectionStart = wordStart;
648+
m_selectionEnd = m_wordAnchorEnd;
649+
} else if (*charPosition >= m_wordAnchorEnd) {
650+
m_selectionStart = m_wordAnchorStart;
651+
m_selectionEnd = wordEnd;
652+
} else {
653+
m_selectionStart = m_wordAnchorStart;
654+
m_selectionEnd = m_wordAnchorEnd;
655+
}
656+
DrawText();
657+
args.Handled(true);
658+
} else if (charPosition != m_selectionEnd) {
659+
m_selectionEnd = charPosition;
660+
DrawText();
661+
args.Handled(true);
662+
}
638663
}
639664
}
640665

@@ -658,6 +683,7 @@ void ParagraphComponentView::OnPointerReleased(
658683
}
659684

660685
m_isSelecting = false;
686+
m_isWordSelecting = false;
661687

662688
ReleasePointerCapture(args.Pointer());
663689

@@ -682,6 +708,7 @@ void ParagraphComponentView::OnPointerCaptureLost() noexcept {
682708
// Pointer capture was lost stop any active selection drag
683709
if (m_isSelecting) {
684710
m_isSelecting = false;
711+
m_isWordSelecting = false;
685712

686713
if (!m_selectionStart || !m_selectionEnd || *m_selectionStart == *m_selectionEnd) {
687714
m_selectionStart = std::nullopt;
@@ -732,12 +759,17 @@ void ParagraphComponentView::CopySelectionToClipboard() noexcept {
732759
winrt::Windows::ApplicationModel::DataTransfer::Clipboard::SetContent(dataPackage);
733760
}
734761

735-
void ParagraphComponentView::SelectWordAtPosition(int32_t charPosition) noexcept {
762+
std::pair<int32_t, int32_t> ParagraphComponentView::GetWordBoundariesAtPosition(int32_t charPosition) noexcept {
736763
const std::wstring utf16Text{facebook::react::WindowsTextLayoutManager::GetTransformedText(m_attributedStringBox)};
737764
const int32_t textLength = static_cast<int32_t>(utf16Text.length());
738765

739-
if (utf16Text.empty() || charPosition < 0 || charPosition >= textLength) {
740-
return;
766+
if (utf16Text.empty() || charPosition < 0) {
767+
return {0, 0};
768+
}
769+
770+
charPosition = std::min(charPosition, textLength - 1);
771+
if (charPosition < 0) {
772+
return {0, 0};
741773
}
742774

743775
int32_t wordStart = charPosition;
@@ -770,6 +802,12 @@ void ParagraphComponentView::SelectWordAtPosition(int32_t charPosition) noexcept
770802
}
771803
}
772804

805+
return {wordStart, wordEnd};
806+
}
807+
808+
void ParagraphComponentView::SelectWordAtPosition(int32_t charPosition) noexcept {
809+
auto [wordStart, wordEnd] = GetWordBoundariesAtPosition(charPosition);
810+
773811
if (wordEnd > wordStart) {
774812
SetSelection(wordStart, wordEnd);
775813
DrawText();

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ struct ParagraphComponentView : ParagraphComponentViewT<ParagraphComponentView,
100100

101101
// Selects the word at the given character position
102102
void SelectWordAtPosition(int32_t charPosition) noexcept;
103+
std::pair<int32_t, int32_t> GetWordBoundariesAtPosition(int32_t charPosition) noexcept;
103104

104105
// Shows a context menu with Copy/Select All options on right-click
105106
void ShowContextMenu() noexcept;
@@ -118,6 +119,11 @@ struct ParagraphComponentView : ParagraphComponentViewT<ParagraphComponentView,
118119
std::optional<int32_t> m_selectionEnd;
119120
bool m_isSelecting{false};
120121

122+
// Double click + drag selection
123+
bool m_isWordSelecting{false};
124+
int32_t m_wordAnchorStart{0};
125+
int32_t m_wordAnchorEnd{0};
126+
121127
// Double-click detection
122128
std::chrono::steady_clock::time_point m_lastClickTime{};
123129
std::optional<int32_t> m_lastClickPosition;

0 commit comments

Comments
 (0)