@@ -95,6 +95,7 @@ abstract class SelectBaseRootState {
9595 touchedInput = $state ( false ) ;
9696 inputNode = $state < HTMLElement | null > ( null ) ;
9797 contentNode = $state < HTMLElement | null > ( null ) ;
98+ viewportNode = $state < HTMLElement | null > ( null ) ;
9899 triggerNode = $state < HTMLElement | null > ( null ) ;
99100 valueId = $state ( "" ) ;
100101 highlightedNode = $state < HTMLElement | null > ( null ) ;
@@ -148,11 +149,32 @@ abstract class SelectBaseRootState {
148149 ) ;
149150 }
150151
151- setHighlightedToFirstCandidate ( ) {
152+ setHighlightedToFirstCandidate ( initial = false ) {
152153 this . setHighlightedNode ( null ) ;
153- const candidateNodes = this . getCandidateNodes ( ) ;
154- if ( ! candidateNodes . length ) return ;
155- this . setHighlightedNode ( candidateNodes [ 0 ] ! ) ;
154+
155+ let nodes = this . getCandidateNodes ( ) ;
156+ if ( ! nodes . length ) return ;
157+
158+ // don't consider nodes that aren't visible within the viewport
159+ if ( this . viewportNode ) {
160+ const viewportRect = this . viewportNode . getBoundingClientRect ( ) ;
161+
162+ nodes = nodes . filter ( ( node ) => {
163+ if ( ! this . viewportNode ) return false ;
164+
165+ const nodeRect = node . getBoundingClientRect ( ) ;
166+
167+ const isNodeFullyVisible =
168+ nodeRect . right < viewportRect . right &&
169+ nodeRect . left > viewportRect . left &&
170+ nodeRect . bottom < viewportRect . bottom &&
171+ nodeRect . top > viewportRect . top ;
172+
173+ return isNodeFullyVisible ;
174+ } ) ;
175+ }
176+
177+ this . setHighlightedNode ( nodes [ 0 ] ! , initial ) ;
156178 }
157179
158180 getNodeByValue ( value : string ) : HTMLElement | null {
@@ -261,9 +283,7 @@ export class SelectSingleRootState extends SelectBaseRootState {
261283 }
262284 }
263285 // if no value is set, we want to highlight the first item
264- const firstCandidate = this . getCandidateNodes ( ) [ 0 ] ;
265- if ( ! firstCandidate ) return ;
266- this . setHighlightedNode ( firstCandidate , true ) ;
286+ this . setHighlightedToFirstCandidate ( true ) ;
267287 } ) ;
268288 }
269289}
@@ -328,9 +348,7 @@ class SelectMultipleRootState extends SelectBaseRootState {
328348 }
329349 }
330350 // if no value is set, we want to highlight the first item
331- const firstCandidate = this . getCandidateNodes ( ) [ 0 ] ;
332- if ( ! firstCandidate ) return ;
333- this . setHighlightedNode ( firstCandidate , true ) ;
351+ this . setHighlightedToFirstCandidate ( true ) ;
334352 } ) ;
335353 }
336354}
@@ -869,7 +887,6 @@ export class SelectContentState {
869887 readonly opts : SelectContentStateOpts ;
870888 readonly root : SelectRoot ;
871889 readonly attachment : RefAttachment ;
872- viewportNode = $state < HTMLElement | null > ( null ) ;
873890 isPositioned = $state ( false ) ;
874891 domContext : DOMContext ;
875892
@@ -1237,7 +1254,9 @@ export class SelectViewportState {
12371254 this . opts = opts ;
12381255 this . content = content ;
12391256 this . root = content . root ;
1240- this . attachment = attachRef ( opts . ref , ( v ) => ( this . content . viewportNode = v ) ) ;
1257+ this . attachment = attachRef ( opts . ref , ( v ) => {
1258+ this . root . viewportNode = v ;
1259+ } ) ;
12411260 }
12421261
12431262 readonly props = $derived . by (
@@ -1371,11 +1390,11 @@ export class SelectScrollDownButtonState {
13711390 this . root = scrollButtonState . root ;
13721391 this . scrollButtonState . onAutoScroll = this . handleAutoScroll ;
13731392
1374- watch ( [ ( ) => this . content . viewportNode , ( ) => this . content . isPositioned ] , ( ) => {
1375- if ( ! this . content . viewportNode || ! this . content . isPositioned ) return ;
1393+ watch ( [ ( ) => this . root . viewportNode , ( ) => this . content . isPositioned ] , ( ) => {
1394+ if ( ! this . root . viewportNode || ! this . content . isPositioned ) return ;
13761395 this . handleScroll ( true ) ;
13771396
1378- return on ( this . content . viewportNode , "scroll" , ( ) => this . handleScroll ( ) ) ;
1397+ return on ( this . root . viewportNode , "scroll" , ( ) => this . handleScroll ( ) ) ;
13791398 } ) ;
13801399
13811400 /**
@@ -1385,11 +1404,11 @@ export class SelectScrollDownButtonState {
13851404 watch (
13861405 [
13871406 ( ) => this . root . opts . inputValue . current ,
1388- ( ) => this . content . viewportNode ,
1407+ ( ) => this . root . viewportNode ,
13891408 ( ) => this . content . isPositioned ,
13901409 ] ,
13911410 ( ) => {
1392- if ( ! this . content . viewportNode || ! this . content . isPositioned ) return ;
1411+ if ( ! this . root . viewportNode || ! this . content . isPositioned ) return ;
13931412 this . handleScroll ( true ) ;
13941413 }
13951414 ) ;
@@ -1416,20 +1435,15 @@ export class SelectScrollDownButtonState {
14161435 if ( ! manual ) {
14171436 this . scrollButtonState . handleUserScroll ( ) ;
14181437 }
1419- if ( ! this . content . viewportNode ) return ;
1420- const maxScroll =
1421- this . content . viewportNode . scrollHeight - this . content . viewportNode . clientHeight ;
1422- const paddingTop = Number . parseInt (
1423- getComputedStyle ( this . content . viewportNode ) . paddingTop ,
1424- 10
1425- ) ;
1438+ if ( ! this . root . viewportNode ) return ;
1439+ const maxScroll = this . root . viewportNode . scrollHeight - this . root . viewportNode . clientHeight ;
1440+ const paddingTop = Number . parseInt ( getComputedStyle ( this . root . viewportNode ) . paddingTop , 10 ) ;
14261441
1427- this . canScrollDown =
1428- Math . ceil ( this . content . viewportNode . scrollTop ) < maxScroll - paddingTop ;
1442+ this . canScrollDown = Math . ceil ( this . root . viewportNode . scrollTop ) < maxScroll - paddingTop ;
14291443 } ;
14301444
14311445 handleAutoScroll = ( ) => {
1432- const viewport = this . content . viewportNode ;
1446+ const viewport = this . root . viewportNode ;
14331447 const selectedItem = this . root . highlightedNode ;
14341448 if ( ! viewport || ! selectedItem ) return ;
14351449 viewport . scrollTop = viewport . scrollTop + selectedItem . offsetHeight ;
@@ -1461,11 +1475,11 @@ export class SelectScrollUpButtonState {
14611475 this . root = scrollButtonState . root ;
14621476 this . scrollButtonState . onAutoScroll = this . handleAutoScroll ;
14631477
1464- watch ( [ ( ) => this . content . viewportNode , ( ) => this . content . isPositioned ] , ( ) => {
1465- if ( ! this . content . viewportNode || ! this . content . isPositioned ) return ;
1478+ watch ( [ ( ) => this . root . viewportNode , ( ) => this . content . isPositioned ] , ( ) => {
1479+ if ( ! this . root . viewportNode || ! this . content . isPositioned ) return ;
14661480
14671481 this . handleScroll ( true ) ;
1468- return on ( this . content . viewportNode , "scroll" , ( ) => this . handleScroll ( ) ) ;
1482+ return on ( this . root . viewportNode , "scroll" , ( ) => this . handleScroll ( ) ) ;
14691483 } ) ;
14701484 }
14711485
@@ -1477,18 +1491,15 @@ export class SelectScrollUpButtonState {
14771491 if ( ! manual ) {
14781492 this . scrollButtonState . handleUserScroll ( ) ;
14791493 }
1480- if ( ! this . content . viewportNode ) return ;
1481- const paddingTop = Number . parseInt (
1482- getComputedStyle ( this . content . viewportNode ) . paddingTop ,
1483- 10
1484- ) ;
1485- this . canScrollUp = this . content . viewportNode . scrollTop - paddingTop > 0.1 ;
1494+ if ( ! this . root . viewportNode ) return ;
1495+ const paddingTop = Number . parseInt ( getComputedStyle ( this . root . viewportNode ) . paddingTop , 10 ) ;
1496+ this . canScrollUp = this . root . viewportNode . scrollTop - paddingTop > 0.1 ;
14861497 } ;
14871498
14881499 handleAutoScroll = ( ) => {
1489- if ( ! this . content . viewportNode || ! this . root . highlightedNode ) return ;
1490- this . content . viewportNode . scrollTop =
1491- this . content . viewportNode . scrollTop - this . root . highlightedNode . offsetHeight ;
1500+ if ( ! this . root . viewportNode || ! this . root . highlightedNode ) return ;
1501+ this . root . viewportNode . scrollTop =
1502+ this . root . viewportNode . scrollTop - this . root . highlightedNode . offsetHeight ;
14921503 } ;
14931504
14941505 readonly props = $derived . by (
0 commit comments