import * as Course from "@hyperschedule/lib/course" ; import * as Row from "@hyperschedule/view/course-row" ; interface Dom { readonly root : HTMLElement ; readonly spacer : HTMLElement ; readonly list : HTMLUListElement ; readonly footer : HTMLElement ; readonly measure : HTMLElement ; } interface State { height : number ; length : number ; get : ( i : number ) => Course . CourseV3 ; rows : Row . Dom [ ] ; isAdded : ( course : Course . CourseV3 ) => boolean ; add : ( course : Course . CourseV3 ) => void ; focus : ( course : Course . CourseV3 ) => void ; } const dom = ( ( ) => { const root = document . getElementById ( "search-results" ) ! ; return < const > { root , spacer : < HTMLElement > root . querySelector ( ".spacer" ) , list : < HTMLUListElement > root . querySelector ( "ul" ) , footer : < HTMLElement > root . querySelector ( "footer" ) , measure : < HTMLElement > root . querySelector ( ".measure" ) , } ; } ) ( ) ; const state = { height : 1 , length : 0 , get : ( ) => undefined ! , rows : [ ] , isAdded : ( ) => false , add : ( ) => undefined , focus : ( ) => undefined , } ; export const update = ( length , get , isAdded , add , focus ) => { state . length = length ; state . get = get ; state . isAdded = isAdded ; state . add = add ; state . focus = focus ; dom . footer . textContent = "End of results" ; render ( ) ; } ; const render = ( ) => { state . height = dom . measure . scrollHeight ; dom . spacer . style . height = `${ state . length * state . height }px` ; const offset = Math . min ( Math . floor ( dom . root . scrollTop / state . height ) , state . length ) ; const limit = Math . min ( Math . ceil ( window . innerHeight / state . height ) , state . length - offset ) ; while ( dom . list . childElementCount < limit ) { const row = Row . create ( Row . Mode . SearchResult ) ; state . rows . push ( row ) ; const wrapper = document . createElement ( "li" ) ; wrapper . className = "slot" ; Row . append ( wrapper , row ) ; dom . list . appendChild ( wrapper ) ; } while ( dom . list . childElementCount > limit ) { dom . list . removeChild ( dom . list . lastElementChild ! ) ; state . rows . pop ( ) ; } for ( let i = 0 ; i < limit ; ++ i ) { const course = state . get ( i + offset ) ; Row . update ( state . rows [ i ] , course , state . isAdded ( course ) , ( ) => state . add ( course ) , ( ) => state . focus ( course ) ) ; ( < HTMLElement > dom . list . children [ i ] ) . style . top = `${ ( i + offset ) * state . height }px` ; } } ; const init = ( ) => { dom . root . addEventListener ( "scroll" , render ) ; window . addEventListener ( "resize" , render ) ; const row = Row . create ( Row . Mode . SearchResult ) ; Row . placeholder ( row ) ; const measure = < HTMLElement > dom . root . querySelector ( ".measure" ) ; Row . append ( measure , row ) ; render ( ) ; } ; init ( ) ;