@@ -20,7 +20,7 @@ export class SearchableDropdown {
2020 // Constants for layout calculations
2121 this . ITEM_HEIGHT = 28 ; // Height per item in pixels (padding + line-height)
2222 this . DEFAULT_MAX_HEIGHT = 300 ; // Default max-height of itemsContainer
23- this . EXPANDED_BOTTOM_MARGIN = 100 ; // Bottom margin when expanded
23+ this . EXPANDED_BOTTOM_MARGIN = 0 ; // Bottom margin when expanded
2424
2525 // Load custom preset icon
2626 this . customPresetIcon = null ;
@@ -445,26 +445,45 @@ export class SearchableDropdown {
445445 applyExpandedState ( ) {
446446 if ( ! this . isExpanded ) return ;
447447
448- // Expand to show all items
449- const neededHeight = this . filteredItems . length * this . ITEM_HEIGHT + 20 ;
450- const maxExpandedHeight = Math . min ( neededHeight , window . innerHeight - this . EXPANDED_BOTTOM_MARGIN ) ;
448+ // Get current dimensions BEFORE making any changes
449+ const currentRect = this . container . getBoundingClientRect ( ) ;
450+ const currentItemsHeight = this . itemsContainer . getBoundingClientRect ( ) . height ;
451451
452- this . itemsContainer . style . maxHeight = `${ maxExpandedHeight } px` ;
453- this . container . style . maxHeight = `${ maxExpandedHeight + 200 } px` ;
454- this . expandButton . textContent = 'Show Less' ;
452+ // Calculate overhead (everything except the items container)
453+ const overhead = currentRect . height - currentItemsHeight ;
454+
455+ // Calculate how much height we need for all items (no extra padding)
456+ const neededHeight = this . filteredItems . length * this . ITEM_HEIGHT ;
457+
458+ // Determine the actual expanded height we'll use for items
459+ const maxAllowedItemsHeight = window . innerHeight - this . EXPANDED_BOTTOM_MARGIN - overhead ;
460+ const expandedItemsHeight = Math . min ( neededHeight , maxAllowedItemsHeight ) ;
461+
462+ // Calculate the total container height (items + overhead)
463+ const totalContainerHeight = expandedItemsHeight + overhead ;
455464
456- // Adjust position to prevent going off-screen
457- requestAnimationFrame ( ( ) => {
458- const containerRect = this . container . getBoundingClientRect ( ) ;
459- const bottomOverflow = containerRect . bottom - window . innerHeight ;
465+ // Use viewport-relative position (getBoundingClientRect gives us position relative to viewport)
466+ const currentViewportTop = currentRect . top ;
467+
468+ // Calculate what the bottom would be if we expand from current viewport position
469+ const potentialBottom = currentViewportTop + totalContainerHeight ;
470+
471+ // If it would overflow viewport, calculate new position BEFORE setting heights
472+ if ( potentialBottom > window . innerHeight - this . EXPANDED_BOTTOM_MARGIN ) {
473+ // Calculate new viewport-relative top position
474+ let newViewportTop = window . innerHeight - totalContainerHeight - this . EXPANDED_BOTTOM_MARGIN ;
475+ // Ensure it doesn't go above screen top
476+ newViewportTop = Math . max ( 10 , newViewportTop ) ;
460477
461- if ( bottomOverflow > 0 ) {
462- const currentTop = parseInt ( this . container . style . top ) ;
463- let newTop = currentTop - bottomOverflow - 10 ;
464- newTop = Math . max ( 10 , newTop ) ;
465- this . container . style . top = `${ newTop } px` ;
466- }
467- } ) ;
478+ // Convert viewport-relative position to document-relative position
479+ const newDocumentTop = newViewportTop + window . pageYOffset ;
480+ this . container . style . top = `${ newDocumentTop } px` ;
481+ }
482+
483+ // Now apply the expanded heights
484+ this . itemsContainer . style . maxHeight = `${ expandedItemsHeight } px` ;
485+ this . container . style . maxHeight = `${ totalContainerHeight } px` ;
486+ this . expandButton . textContent = 'Show Less' ;
468487 }
469488
470489 /**
@@ -479,31 +498,45 @@ export class SearchableDropdown {
479498 }
480499
481500 if ( this . isExpanded ) {
482- // Expand to show all items
483- const neededHeight = this . filteredItems . length * this . ITEM_HEIGHT + 20 ;
484- const maxExpandedHeight = Math . min ( neededHeight , window . innerHeight - this . EXPANDED_BOTTOM_MARGIN ) ;
501+ // Get current dimensions BEFORE making any changes
502+ const currentRect = this . container . getBoundingClientRect ( ) ;
503+ const currentItemsHeight = this . itemsContainer . getBoundingClientRect ( ) . height ;
485504
486- this . itemsContainer . style . maxHeight = `${ maxExpandedHeight } px` ;
487- this . container . style . maxHeight = `${ maxExpandedHeight + 200 } px` ;
488- this . expandButton . textContent = 'Show Less' ;
505+ // Calculate overhead (everything except the items container)
506+ const overhead = currentRect . height - currentItemsHeight ;
489507
490- // Adjust position to prevent going off-screen
491- // Need to wait for next frame to get accurate dimensions after height change
492- requestAnimationFrame ( ( ) => {
493- const containerRect = this . container . getBoundingClientRect ( ) ;
494- const bottomOverflow = containerRect . bottom - window . innerHeight ;
508+ // Calculate how much height we need for all items (no extra padding)
509+ const neededHeight = this . filteredItems . length * this . ITEM_HEIGHT ;
510+
511+ // Determine the actual expanded height we'll use for items
512+ const maxAllowedItemsHeight = window . innerHeight - this . EXPANDED_BOTTOM_MARGIN - overhead ;
513+ const expandedItemsHeight = Math . min ( neededHeight , maxAllowedItemsHeight ) ;
514+
515+ // Calculate the total container height (items + overhead)
516+ const totalContainerHeight = expandedItemsHeight + overhead ;
517+
518+ // Use viewport-relative position (getBoundingClientRect gives us position relative to viewport)
519+ const currentViewportTop = currentRect . top ;
520+
521+ // Calculate what the bottom would be if we expand from current viewport position
522+ const potentialBottom = currentViewportTop + totalContainerHeight ;
523+
524+ // If it would overflow viewport, calculate new position BEFORE setting heights
525+ if ( potentialBottom > window . innerHeight - this . EXPANDED_BOTTOM_MARGIN ) {
526+ // Calculate new viewport-relative top position
527+ let newViewportTop = window . innerHeight - totalContainerHeight - this . EXPANDED_BOTTOM_MARGIN ;
528+ // Ensure it doesn't go above screen top
529+ newViewportTop = Math . max ( 10 , newViewportTop ) ;
495530
496- if ( bottomOverflow > 0 ) {
497- // Move container up to fit on screen
498- const currentTop = parseInt ( this . container . style . top ) ;
499- let newTop = currentTop - bottomOverflow - 10 ; // 10px margin from bottom
500-
501- // Ensure it doesn't go above screen top
502- newTop = Math . max ( 10 , newTop ) ;
503-
504- this . container . style . top = `${ newTop } px` ;
505- }
506- } ) ;
531+ // Convert viewport-relative position to document-relative position
532+ const newDocumentTop = newViewportTop + window . pageYOffset ;
533+ this . container . style . top = `${ newDocumentTop } px` ;
534+ }
535+
536+ // Now apply the expanded heights
537+ this . itemsContainer . style . maxHeight = `${ expandedItemsHeight } px` ;
538+ this . container . style . maxHeight = `${ totalContainerHeight } px` ;
539+ this . expandButton . textContent = 'Show Less' ;
507540 } else {
508541 // Collapse back to default
509542 this . itemsContainer . style . maxHeight = `${ this . DEFAULT_MAX_HEIGHT } px` ;
0 commit comments