Browse Source

update highlight selection on mobile

imwald
Silberengel 4 months ago
parent
commit
4ed27d8e7c
  1. 115
      src/components/RssFeedItem/index.tsx

115
src/components/RssFeedItem/index.tsx

@ -50,10 +50,14 @@ export default function RssFeedItem({ item, className }: { item: TRssFeedItem; c
const [isExpanded, setIsExpanded] = useState(false) const [isExpanded, setIsExpanded] = useState(false)
const contentRef = useRef<HTMLDivElement>(null) const contentRef = useRef<HTMLDivElement>(null)
const selectionTimeoutRef = useRef<NodeJS.Timeout>() const selectionTimeoutRef = useRef<NodeJS.Timeout>()
const isSelectingRef = useRef(false)
const touchEndTimeoutRef = useRef<NodeJS.Timeout>()
const lastSelectionChangeRef = useRef<number>(0)
const selectionStableTimeoutRef = useRef<NodeJS.Timeout>()
// Handle text selection // Handle text selection
useEffect(() => { useEffect(() => {
const handleSelection = () => { const handleSelection = (forceShow = false) => {
const selection = window.getSelection() const selection = window.getSelection()
if (!selection || selection.rangeCount === 0) { if (!selection || selection.rangeCount === 0) {
setShowHighlightButton(false) setShowHighlightButton(false)
@ -131,10 +135,14 @@ export default function RssFeedItem({ item, className }: { item: TRssFeedItem; c
if (text.length > 0) { if (text.length > 0) {
setSelectedText(text) setSelectedText(text)
// On mobile, show drawer; on desktop, show floating button // On mobile, only show drawer after selection is complete (not while actively selecting)
// On desktop, show floating button immediately
if (isSmallScreen) { if (isSmallScreen) {
setShowHighlightDrawer(true) // On mobile, wait until user finishes selecting before showing drawer
setShowHighlightButton(false) if (forceShow || !isSelectingRef.current) {
setShowHighlightDrawer(true)
setShowHighlightButton(false)
}
} else { } else {
// Get selection position for button placement // Get selection position for button placement
const rect = range.getBoundingClientRect() const rect = range.getBoundingClientRect()
@ -163,7 +171,7 @@ export default function RssFeedItem({ item, className }: { item: TRssFeedItem; c
if (selectionTimeoutRef.current) { if (selectionTimeoutRef.current) {
clearTimeout(selectionTimeoutRef.current) clearTimeout(selectionTimeoutRef.current)
} }
selectionTimeoutRef.current = setTimeout(handleSelection, 50) selectionTimeoutRef.current = setTimeout(() => handleSelection(true), 50)
} }
const handleClick = (e: MouseEvent) => { const handleClick = (e: MouseEvent) => {
@ -180,18 +188,103 @@ export default function RssFeedItem({ item, className }: { item: TRssFeedItem; c
} }
} }
// Handle touch events for mobile
const handleTouchStart = () => {
if (isSmallScreen) {
isSelectingRef.current = true
// Clear any pending drawer show
setShowHighlightDrawer(false)
}
}
const handleTouchMove = () => {
if (isSmallScreen) {
isSelectingRef.current = true
// Clear any pending drawer show while actively selecting
if (touchEndTimeoutRef.current) {
clearTimeout(touchEndTimeoutRef.current)
}
}
}
const handleTouchEnd = () => {
if (isSmallScreen) {
// Wait a bit longer on mobile to allow native selection UI to appear first
if (touchEndTimeoutRef.current) {
clearTimeout(touchEndTimeoutRef.current)
}
touchEndTimeoutRef.current = setTimeout(() => {
isSelectingRef.current = false
// Don't immediately show drawer - let selection stability check handle it
// This allows user to continue dragging selection handles if needed
}, 200) // Shorter delay since we're using stability check
}
}
// Also listen for selectionchange events which fire more reliably // Also listen for selectionchange events which fire more reliably
const handleSelectionChange = () => { const handleSelectionChange = () => {
if (selectionTimeoutRef.current) { if (isSmallScreen) {
clearTimeout(selectionTimeoutRef.current) // On mobile, track when selection last changed
lastSelectionChangeRef.current = Date.now()
// Clear any pending drawer shows
if (selectionStableTimeoutRef.current) {
clearTimeout(selectionStableTimeoutRef.current)
}
setShowHighlightDrawer(false)
// If we're actively selecting (touch events), don't process yet
if (isSelectingRef.current) {
return
}
// Wait for selection to be stable (no changes for 500ms) before showing drawer
selectionStableTimeoutRef.current = setTimeout(() => {
const timeSinceLastChange = Date.now() - lastSelectionChangeRef.current
// Only show if selection hasn't changed in the last 500ms
if (timeSinceLastChange >= 500 && !isSelectingRef.current) {
handleSelection(true)
}
}, 500)
} else {
// Desktop: shorter delay
if (selectionTimeoutRef.current) {
clearTimeout(selectionTimeoutRef.current)
}
selectionTimeoutRef.current = setTimeout(() => handleSelection(true), 50)
} }
selectionTimeoutRef.current = setTimeout(handleSelection, 50)
} }
document.addEventListener('mouseup', handleMouseUp) document.addEventListener('mouseup', handleMouseUp)
document.addEventListener('click', handleClick, true) // Use capture phase document.addEventListener('click', handleClick, true) // Use capture phase
document.addEventListener('selectionchange', handleSelectionChange) document.addEventListener('selectionchange', handleSelectionChange)
// Add touch event listeners for mobile
if (isSmallScreen && contentRef.current) {
const contentElement = contentRef.current
contentElement.addEventListener('touchstart', handleTouchStart, { passive: true })
contentElement.addEventListener('touchmove', handleTouchMove, { passive: true })
contentElement.addEventListener('touchend', handleTouchEnd, { passive: true })
return () => {
document.removeEventListener('mouseup', handleMouseUp)
document.removeEventListener('click', handleClick, true)
document.removeEventListener('selectionchange', handleSelectionChange)
contentElement.removeEventListener('touchstart', handleTouchStart)
contentElement.removeEventListener('touchmove', handleTouchMove)
contentElement.removeEventListener('touchend', handleTouchEnd)
if (selectionTimeoutRef.current) {
clearTimeout(selectionTimeoutRef.current)
}
if (touchEndTimeoutRef.current) {
clearTimeout(touchEndTimeoutRef.current)
}
if (selectionStableTimeoutRef.current) {
clearTimeout(selectionStableTimeoutRef.current)
}
}
}
return () => { return () => {
document.removeEventListener('mouseup', handleMouseUp) document.removeEventListener('mouseup', handleMouseUp)
document.removeEventListener('click', handleClick, true) document.removeEventListener('click', handleClick, true)
@ -199,6 +292,12 @@ export default function RssFeedItem({ item, className }: { item: TRssFeedItem; c
if (selectionTimeoutRef.current) { if (selectionTimeoutRef.current) {
clearTimeout(selectionTimeoutRef.current) clearTimeout(selectionTimeoutRef.current)
} }
if (touchEndTimeoutRef.current) {
clearTimeout(touchEndTimeoutRef.current)
}
if (selectionStableTimeoutRef.current) {
clearTimeout(selectionStableTimeoutRef.current)
}
} }
}, [showHighlightButton, isSmallScreen]) }, [showHighlightButton, isSmallScreen])

Loading…
Cancel
Save