@@ -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 ();
0 commit comments