11import { type FileContents , File , FileOptions , LineAnnotation , type SelectedLineRange } from "@pierre/diffs"
22import { ComponentProps , createEffect , createMemo , createSignal , onCleanup , onMount , Show , splitProps } from "solid-js"
3+ import { Portal } from "solid-js/web"
34import { createDefaultOptions , styleVariables } from "../pierre"
45import { getWorkerPool } from "../pierre/worker"
56import { Icon } from "./icon"
@@ -125,11 +126,9 @@ export function Code<T>(props: CodeProps<T>) {
125126 let wrapper ! : HTMLDivElement
126127 let container ! : HTMLDivElement
127128 let findInput : HTMLInputElement | undefined
128- let findBar : HTMLDivElement | undefined
129129 let findOverlay ! : HTMLDivElement
130130 let findOverlayFrame : number | undefined
131131 let findOverlayScroll : HTMLElement [ ] = [ ]
132- let findScroll : HTMLElement | undefined
133132 let observer : MutationObserver | undefined
134133 let renderToken = 0
135134 let selectionFrame : number | undefined
@@ -159,6 +158,8 @@ export function Code<T>(props: CodeProps<T>) {
159158 let findMode : "highlights" | "overlay" = "overlay"
160159 let findHits : Range [ ] = [ ]
161160
161+ const [ findPos , setFindPos ] = createSignal < { top : number ; right : number } > ( { top : 8 , right : 8 } )
162+
162163 const file = createMemo (
163164 ( ) =>
164165 new File < T > (
@@ -291,23 +292,26 @@ export function Code<T>(props: CodeProps<T>) {
291292 setFindIndex ( 0 )
292293 }
293294
294- const getScrollParent = ( el : HTMLElement ) : HTMLElement | null => {
295+ const getScrollParent = ( el : HTMLElement ) : HTMLElement | undefined => {
295296 let parent = el . parentElement
296297 while ( parent ) {
297298 const style = getComputedStyle ( parent )
298299 if ( style . overflowY === "auto" || style . overflowY === "scroll" ) return parent
299300 parent = parent . parentElement
300301 }
301- return null
302302 }
303303
304304 const positionFindBar = ( ) => {
305- if ( ! findBar || ! wrapper ) return
306- const scrollTop = findScroll ? findScroll . scrollTop : window . scrollY
307- findBar . style . position = "absolute"
308- findBar . style . top = `${ scrollTop + 8 } px`
309- findBar . style . right = "8px"
310- findBar . style . left = ""
305+ if ( typeof window === "undefined" ) return
306+
307+ const root = getScrollParent ( wrapper ) ?? wrapper
308+ const rect = root . getBoundingClientRect ( )
309+ const title = parseFloat ( getComputedStyle ( root ) . getPropertyValue ( "--session-title-height" ) )
310+ const header = Number . isNaN ( title ) ? 0 : title
311+ setFindPos ( {
312+ top : Math . round ( rect . top ) + header - 4 ,
313+ right : Math . round ( window . innerWidth - rect . right ) + 8 ,
314+ } )
311315 }
312316
313317 const scanFind = ( root : ShadowRoot , query : string ) => {
@@ -426,7 +430,6 @@ export function Code<T>(props: CodeProps<T>) {
426430 }
427431 if ( opts ?. scroll && active ) {
428432 scrollToRange ( active )
429- positionFindBar ( )
430433 }
431434 return
432435 }
@@ -435,7 +438,6 @@ export function Code<T>(props: CodeProps<T>) {
435438 syncOverlayScroll ( )
436439 if ( opts ?. scroll && active ) {
437440 scrollToRange ( active )
438- positionFindBar ( )
439441 }
440442 scheduleOverlay ( )
441443 }
@@ -464,14 +466,12 @@ export function Code<T>(props: CodeProps<T>) {
464466 return
465467 }
466468 scrollToRange ( active )
467- positionFindBar ( )
468469 return
469470 }
470471
471472 clearHighlightFind ( )
472473 syncOverlayScroll ( )
473474 scrollToRange ( active )
474- positionFindBar ( )
475475 scheduleOverlay ( )
476476 }
477477
@@ -484,11 +484,9 @@ export function Code<T>(props: CodeProps<T>) {
484484 findCurrent = host
485485 findTarget = host
486486
487- findScroll = getScrollParent ( wrapper ) ?? undefined
488487 if ( ! findOpen ( ) ) setFindOpen ( true )
489488 requestAnimationFrame ( ( ) => {
490489 applyFind ( { scroll : true } )
491- positionFindBar ( )
492490 findInput ?. focus ( )
493491 findInput ?. select ( )
494492 } )
@@ -514,18 +512,18 @@ export function Code<T>(props: CodeProps<T>) {
514512
515513 createEffect ( ( ) => {
516514 if ( ! findOpen ( ) ) return
517- findScroll = getScrollParent ( wrapper ) ?? undefined
518- const target = findScroll ?? window
519515
520- const handler = ( ) => positionFindBar ( )
521- target . addEventListener ( "scroll" , handler , { passive : true } )
522- window . addEventListener ( "resize" , handler , { passive : true } )
523- handler ( )
516+ const update = ( ) => positionFindBar ( )
517+ requestAnimationFrame ( update )
518+ window . addEventListener ( "resize" , update , { passive : true } )
519+
520+ const root = getScrollParent ( wrapper ) ?? wrapper
521+ const observer = typeof ResizeObserver === "undefined" ? undefined : new ResizeObserver ( ( ) => update ( ) )
522+ observer ?. observe ( root )
524523
525524 onCleanup ( ( ) => {
526- target . removeEventListener ( "scroll" , handler )
527- window . removeEventListener ( "resize" , handler )
528- findScroll = undefined
525+ window . removeEventListener ( "resize" , update )
526+ observer ?. disconnect ( )
529527 } )
530528 } )
531529
@@ -916,6 +914,64 @@ export function Code<T>(props: CodeProps<T>) {
916914 pendingSelectionEnd = false
917915 } )
918916
917+ const FindBar = ( barProps : { class : string ; style ?: ComponentProps < "div" > [ "style" ] } ) => (
918+ < div class = { barProps . class } style = { barProps . style } onPointerDown = { ( e ) => e . stopPropagation ( ) } >
919+ < Icon name = "magnifying-glass" size = "small" class = "text-text-weak shrink-0" />
920+ < input
921+ ref = { findInput }
922+ placeholder = "Find"
923+ value = { findQuery ( ) }
924+ class = "w-40 bg-transparent outline-none text-14-regular text-text-strong placeholder:text-text-weak"
925+ onInput = { ( e ) => {
926+ setFindQuery ( e . currentTarget . value )
927+ setFindIndex ( 0 )
928+ applyFind ( { reset : true , scroll : true } )
929+ } }
930+ onKeyDown = { ( e ) => {
931+ if ( e . key === "Escape" ) {
932+ e . preventDefault ( )
933+ closeFind ( )
934+ return
935+ }
936+ if ( e . key !== "Enter" ) return
937+ e . preventDefault ( )
938+ stepFind ( e . shiftKey ? - 1 : 1 )
939+ } }
940+ />
941+ < div class = "shrink-0 text-12-regular text-text-weak tabular-nums text-right" style = { { width : "10ch" } } >
942+ { findCount ( ) ? `${ findIndex ( ) + 1 } /${ findCount ( ) } ` : "0/0" }
943+ </ div >
944+ < div class = "flex items-center" >
945+ < button
946+ type = "button"
947+ class = "size-6 grid place-items-center rounded text-text-weak hover:bg-surface-base-hover hover:text-text-strong disabled:opacity-40 disabled:pointer-events-none"
948+ disabled = { findCount ( ) === 0 }
949+ aria-label = "Previous match"
950+ onClick = { ( ) => stepFind ( - 1 ) }
951+ >
952+ < Icon name = "chevron-down" size = "small" class = "rotate-180" />
953+ </ button >
954+ < button
955+ type = "button"
956+ class = "size-6 grid place-items-center rounded text-text-weak hover:bg-surface-base-hover hover:text-text-strong disabled:opacity-40 disabled:pointer-events-none"
957+ disabled = { findCount ( ) === 0 }
958+ aria-label = "Next match"
959+ onClick = { ( ) => stepFind ( 1 ) }
960+ >
961+ < Icon name = "chevron-down" size = "small" />
962+ </ button >
963+ </ div >
964+ < button
965+ type = "button"
966+ class = "size-6 grid place-items-center rounded text-text-weak hover:bg-surface-base-hover hover:text-text-strong"
967+ aria-label = "Close search"
968+ onClick = { closeFind }
969+ >
970+ < Icon name = "close-small" size = "small" />
971+ </ button >
972+ </ div >
973+ )
974+
919975 return (
920976 < div
921977 data-component = "code"
@@ -936,65 +992,15 @@ export function Code<T>(props: CodeProps<T>) {
936992 } }
937993 >
938994 < Show when = { findOpen ( ) } >
939- < div
940- ref = { findBar }
941- class = "z-50 flex h-8 items-center gap-2 rounded-md border border-border-base bg-background-base px-3 shadow-md"
942- onPointerDown = { ( e ) => e . stopPropagation ( ) }
943- >
944- < Icon name = "magnifying-glass" size = "small" class = "text-text-weak shrink-0" />
945- < input
946- ref = { findInput }
947- placeholder = "Find"
948- value = { findQuery ( ) }
949- class = "w-40 bg-transparent outline-none text-14-regular text-text-strong placeholder:text-text-weak"
950- onInput = { ( e ) => {
951- setFindQuery ( e . currentTarget . value )
952- setFindIndex ( 0 )
953- applyFind ( { reset : true , scroll : true } )
954- } }
955- onKeyDown = { ( e ) => {
956- if ( e . key === "Escape" ) {
957- e . preventDefault ( )
958- closeFind ( )
959- return
960- }
961- if ( e . key !== "Enter" ) return
962- e . preventDefault ( )
963- stepFind ( e . shiftKey ? - 1 : 1 )
995+ < Portal >
996+ < FindBar
997+ class = "fixed z-50 flex h-8 items-center gap-2 rounded-md border border-border-base bg-background-base px-3 shadow-md"
998+ style = { {
999+ top : `${ findPos ( ) . top } px` ,
1000+ right : `${ findPos ( ) . right } px` ,
9641001 } }
9651002 />
966- < div class = "shrink-0 text-12-regular text-text-weak tabular-nums text-right" style = { { width : "10ch" } } >
967- { findCount ( ) ? `${ findIndex ( ) + 1 } /${ findCount ( ) } ` : "0/0" }
968- </ div >
969- < div class = "flex items-center" >
970- < button
971- type = "button"
972- class = "size-6 grid place-items-center rounded text-text-weak hover:bg-surface-base-hover hover:text-text-strong disabled:opacity-40 disabled:pointer-events-none"
973- disabled = { findCount ( ) === 0 }
974- aria-label = "Previous match"
975- onClick = { ( ) => stepFind ( - 1 ) }
976- >
977- < Icon name = "chevron-down" size = "small" class = "rotate-180" />
978- </ button >
979- < button
980- type = "button"
981- class = "size-6 grid place-items-center rounded text-text-weak hover:bg-surface-base-hover hover:text-text-strong disabled:opacity-40 disabled:pointer-events-none"
982- disabled = { findCount ( ) === 0 }
983- aria-label = "Next match"
984- onClick = { ( ) => stepFind ( 1 ) }
985- >
986- < Icon name = "chevron-down" size = "small" />
987- </ button >
988- </ div >
989- < button
990- type = "button"
991- class = "size-6 grid place-items-center rounded text-text-weak hover:bg-surface-base-hover hover:text-text-strong"
992- aria-label = "Close search"
993- onClick = { closeFind }
994- >
995- < Icon name = "close-small" size = "small" />
996- </ button >
997- </ div >
1003+ </ Portal >
9981004 </ Show >
9991005 < div ref = { container } />
10001006 < div ref = { findOverlay } class = "pointer-events-none absolute inset-0 z-0" />
0 commit comments