@@ -40,7 +40,7 @@ const TB_PANEL_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24
4040 * @param {string } cfg.rootClass - The app's root class (for scoping the cursor rule).
4141 * @returns {{
4242 * highlightElem: HTMLSpanElement, toolbarElem: HTMLSpanElement,
43- * getSelectedOverlayWords: () => Array<import('../viewerWordObjects.js').UiOcrWord>, updateCommentIcons: () => void,
43+ * updateCommentIcons: () => void,
4444 * installBehaviors: () => (() => void)
4545 * }}
4646 */
@@ -64,8 +64,14 @@ export function createHighlightTool(scribe, rootElem, { colors, defaultColor, ro
6464 const setTipColor = ( c ) => { if ( tipPath && c ) tipPath . style . fill = c ; } ;
6565 setTipColor ( highlightColor ) ;
6666
67- /** Toggle the highlighter cursor on the overlay words when highlight mode is active. */
67+ /** Toggle the highlighter cursor over the page's text when highlight mode is active. */
6868 function updateHighlightCursorStyle ( ) {
69+ if ( scribe . useCustomSelection ) {
70+ // No word elements to hang a cursor rule on: the selection engine sets the container's cursor.
71+ scribe . textSel . cursorOverride = highlightMode ? HIGHLIGHT_CURSOR : null ;
72+ if ( ! highlightMode ) scribe . scrollContainer . style . cursor = '' ;
73+ return ;
74+ }
6975 if ( ! cursorStyleElem ) {
7076 cursorStyleElem = document . createElement ( 'style' ) ;
7177 document . head . appendChild ( cursorStyleElem ) ;
@@ -75,16 +81,11 @@ export function createHighlightTool(scribe, rootElem, { colors, defaultColor, ro
7581 : '' ;
7682 }
7783
78- /** UiOcrWord objects under the current browser text selection (via the HTML overlay). */
79- function getSelectedOverlayWords ( ) {
80- return scribe . getWordsUnderTextSelection ( ) ;
81- }
82-
8384 function applyToSelection ( ) {
84- const matchedWords = getSelectedOverlayWords ( ) ;
85+ const matchedWords = scribe . getWordsUnderTextSelection ( ) ;
8586 if ( matchedWords . length === 0 || ! highlightColor ) return false ;
8687 applyHighlight ( scribe , matchedWords , highlightColor , 0.5 ) ;
87- window . getSelection ( ) ?. removeAllRanges ( ) ;
88+ scribe . clearTextSelection ( ) ;
8889 return true ;
8990 }
9091
@@ -643,6 +644,17 @@ export function createHighlightTool(scribe, rootElem, { colors, defaultColor, ro
643644 cmtText . focus ( ) ;
644645 } ;
645646
647+ /**
648+ * The highlighted word under an event's pointer.
649+ * @param {MouseEvent } event
650+ */
651+ const highlightWordAt = ( event ) => {
652+ // The custom engine has no word spans, so hit-test the highlight by page geometry rather than event.target.
653+ if ( scribe . useCustomSelection ) return scribe . textSel . hitTestHighlight ( event . clientX , event . clientY ) ?. kw ?? null ;
654+ const wordEl = /** @type {Element } */ ( event . target ) . closest ( '.scribe-word' ) ;
655+ return wordEl ? /** @type {any } */ ( wordEl ) . _scribeObj : null ;
656+ } ;
657+
646658 /** Resolve the card target under an event: a comment mark, a note mark, or a commented word. */
647659 const cmtTargetFromEvent = ( event ) => {
648660 if ( ! ( event . target instanceof Element ) ) return null ;
@@ -659,8 +671,7 @@ export function createHighlightTool(scribe, rootElem, { colors, defaultColor, ro
659671 const annot = ( scribe . doc . annotations . pages [ n ] || [ ] ) . filter ( ( a ) => a . type === 'text' ) [ Number ( noteEl . dataset . noteIdx ) ] ;
660672 return annot ? { kind : 'note' , annot, n } : null ;
661673 }
662- const wordEl = event . target . closest ( '.scribe-word' ) ;
663- const kw = wordEl && /** @type {any } */ ( wordEl ) . _scribeObj ;
674+ const kw = highlightWordAt ( event ) ;
664675 if ( kw && kw . highlightGroupId && kw . highlightComment ) {
665676 return {
666677 kind : 'highlight' , kw, groupId : kw . highlightGroupId , n : kw . word . line . page . n ,
@@ -671,6 +682,19 @@ export function createHighlightTool(scribe, rootElem, { colors, defaultColor, ro
671682
672683 const cmtOver = ( event ) => { const t = cmtTargetFromEvent ( event ) ; if ( t ) cmtShow ( t ) ; } ;
673684 const cmtOut = ( event ) => { if ( cmtTargetFromEvent ( event ) ) cmtScheduleHide ( ) ; } ;
685+ // With no word spans, the pointer crosses no element boundary over the text,
686+ // so a hovered commented highlight must be sampled on pointer move rather than delegated from mouseover.
687+ let cmtMoveRaf = null ;
688+ const cmtMove = ( event ) => {
689+ if ( cmtMoveRaf !== null ) return ;
690+ const { clientX, clientY, target } = event ;
691+ cmtMoveRaf = requestAnimationFrame ( ( ) => {
692+ cmtMoveRaf = null ;
693+ const t = cmtTargetFromEvent ( { clientX, clientY, target } ) ;
694+ if ( t ) cmtShow ( t ) ;
695+ else if ( cmtTarget && cmtTarget . kind === 'highlight' ) cmtScheduleHide ( ) ;
696+ } ) ;
697+ } ;
674698 const cmtPress = ( event ) => {
675699 if ( ! ( event . target instanceof Element ) ) return ;
676700 const el = event . target . closest ( '.scribe-hl-cmark, .scribe-note-icon' ) ;
@@ -683,12 +707,10 @@ export function createHighlightTool(scribe, rootElem, { colors, defaultColor, ro
683707 return ;
684708 }
685709 // Gate on the color, not the comment: the card's footer is the only place to recolor or delete an uncommented highlight.
686- const wordEl = event . target . closest ( '.scribe-word' ) ;
687- const kw = wordEl && /** @type {any } */ ( wordEl ) . _scribeObj ;
710+ const kw = highlightWordAt ( event ) ;
688711 if ( ! kw || ! kw . highlightColor ) return ;
689712 // A drag that leaves a text selection is a selection gesture, not a click on the object.
690- const sel = window . getSelection ( ) ;
691- if ( sel && ! sel . isCollapsed ) return ;
713+ if ( scribe . hasTextSelection ( ) ) return ;
692714 event . stopPropagation ( ) ;
693715 cmtPin ( {
694716 kind : 'highlight' , kw, groupId : kw . highlightGroupId || null , n : kw . word . line . page . n ,
@@ -704,6 +726,7 @@ export function createHighlightTool(scribe, rootElem, { colors, defaultColor, ro
704726 } ;
705727 scribe . elem . addEventListener ( 'mouseover' , cmtOver ) ;
706728 scribe . elem . addEventListener ( 'mouseout' , cmtOut ) ;
729+ if ( scribe . useCustomSelection ) scribe . elem . addEventListener ( 'mousemove' , cmtMove ) ;
707730 scribe . elem . addEventListener ( 'click' , cmtPress ) ;
708731 scribe . elem . addEventListener ( 'focusin' , cmtOver ) ;
709732 scribe . elem . addEventListener ( 'focusout' , cmtOut ) ;
@@ -888,6 +911,7 @@ export function createHighlightTool(scribe, rootElem, { colors, defaultColor, ro
888911 commentObserver . disconnect ( ) ;
889912 scribe . elem . removeEventListener ( 'mouseover' , cmtOver ) ;
890913 scribe . elem . removeEventListener ( 'mouseout' , cmtOut ) ;
914+ scribe . elem . removeEventListener ( 'mousemove' , cmtMove ) ;
891915 scribe . elem . removeEventListener ( 'click' , cmtPress ) ;
892916 scribe . elem . removeEventListener ( 'focusin' , cmtOver ) ;
893917 scribe . elem . removeEventListener ( 'focusout' , cmtOut ) ;
@@ -914,7 +938,6 @@ export function createHighlightTool(scribe, rootElem, { colors, defaultColor, ro
914938 return {
915939 highlightElem,
916940 toolbarElem,
917- getSelectedOverlayWords,
918941 updateCommentIcons,
919942 installBehaviors,
920943 } ;
0 commit comments