@@ -58,6 +58,7 @@ export class Yasqe extends CodeMirror {
5858 private resizeWrapper ?: HTMLDivElement ;
5959 private snippetsBar ?: HTMLDivElement ;
6060 private snippetsClickHandler ?: ( e : MouseEvent ) => void ;
61+ private snippetsResizeHandler ?: ( ) => void ;
6162 public rootEl : HTMLDivElement ;
6263 public storage : YStorage ;
6364 public config : Config ;
@@ -468,98 +469,241 @@ export class Yasqe extends CodeMirror {
468469
469470 const snippets = this . config . snippets ;
470471
471- // If 10 or fewer snippets, show all as buttons
472- if ( snippets . length <= 10 ) {
473- snippets . forEach ( ( snippet ) => {
474- const btn = document . createElement ( "button" ) ;
475- addClass ( btn , "yasqe_snippetButton" ) ;
476- btn . textContent = snippet . label ;
477- btn . title = snippet . code ;
478- btn . setAttribute ( "aria-label" , `Insert ${ snippet . label } snippet` ) ;
479- btn . onclick = ( ) => this . insertSnippet ( snippet . code ) ;
480- this . snippetsBar ! . appendChild ( btn ) ;
481- } ) ;
482- } else {
483- // Group snippets by group property
484- const grouped : { [ group : string ] : Snippet [ ] } = { } ;
485- const ungrouped : Snippet [ ] = [ ] ;
486-
487- snippets . forEach ( ( snippet ) => {
488- if ( snippet . group ) {
489- if ( ! grouped [ snippet . group ] ) grouped [ snippet . group ] = [ ] ;
490- grouped [ snippet . group ] . push ( snippet ) ;
491- } else {
492- ungrouped . push ( snippet ) ;
493- }
472+ // Create a container for visible items
473+ const visibleContainer = document . createElement ( "div" ) ;
474+ addClass ( visibleContainer , "yasqe_snippetsVisible" ) ;
475+
476+ // Create all buttons/dropdowns
477+ const allItems : HTMLElement [ ] = [ ] ;
478+
479+ // Group snippets by group property for all snippets (not just >10)
480+ const grouped : { [ group : string ] : Snippet [ ] } = { } ;
481+ const ungrouped : Snippet [ ] = [ ] ;
482+
483+ snippets . forEach ( ( snippet ) => {
484+ if ( snippet . group ) {
485+ if ( ! grouped [ snippet . group ] ) grouped [ snippet . group ] = [ ] ;
486+ grouped [ snippet . group ] . push ( snippet ) ;
487+ } else {
488+ ungrouped . push ( snippet ) ;
489+ }
490+ } ) ;
491+
492+ // Create dropdown for each group
493+ Object . keys ( grouped ) . forEach ( ( groupName ) => {
494+ const dropdown = document . createElement ( "div" ) ;
495+ addClass ( dropdown , "yasqe_snippetDropdown" ) ;
496+
497+ const dropdownBtn = document . createElement ( "button" ) ;
498+ addClass ( dropdownBtn , "yasqe_snippetDropdownButton" ) ;
499+ dropdownBtn . textContent = groupName + " " ;
500+ const chevron = drawSvgStringAsElement ( imgs . chevronDown ) ;
501+ addClass ( chevron , "chevronIcon" ) ;
502+ dropdownBtn . appendChild ( chevron ) ;
503+ dropdownBtn . setAttribute ( "aria-label" , `${ groupName } snippets` ) ;
504+ dropdownBtn . setAttribute ( "aria-expanded" , "false" ) ;
505+
506+ const dropdownContent = document . createElement ( "div" ) ;
507+ addClass ( dropdownContent , "yasqe_snippetDropdownContent" ) ;
508+ dropdownContent . setAttribute ( "role" , "menu" ) ;
509+
510+ grouped [ groupName ] . forEach ( ( snippet ) => {
511+ const item = document . createElement ( "button" ) ;
512+ addClass ( item , "yasqe_snippetDropdownItem" ) ;
513+ item . textContent = snippet . label ;
514+ item . title = snippet . code ;
515+ item . setAttribute ( "role" , "menuitem" ) ;
516+ item . onclick = ( ) => {
517+ this . insertSnippet ( snippet . code ) ;
518+ dropdownContent . style . display = "none" ;
519+ dropdownBtn . setAttribute ( "aria-expanded" , "false" ) ;
520+ } ;
521+ dropdownContent . appendChild ( item ) ;
494522 } ) ;
495523
496- // Create dropdown for each group
497- Object . keys ( grouped ) . forEach ( ( groupName ) => {
498- const dropdown = document . createElement ( "div" ) ;
499- addClass ( dropdown , "yasqe_snippetDropdown" ) ;
500-
501- const dropdownBtn = document . createElement ( "button" ) ;
502- addClass ( dropdownBtn , "yasqe_snippetDropdownButton" ) ;
503- dropdownBtn . textContent = groupName + " " ;
504- const chevron = drawSvgStringAsElement ( imgs . chevronDown ) ;
505- addClass ( chevron , "chevronIcon" ) ;
506- dropdownBtn . appendChild ( chevron ) ;
507- dropdownBtn . setAttribute ( "aria-label" , `${ groupName } snippets` ) ;
508- dropdownBtn . setAttribute ( "aria-expanded" , "false" ) ;
509-
510- const dropdownContent = document . createElement ( "div" ) ;
511- addClass ( dropdownContent , "yasqe_snippetDropdownContent" ) ;
512- dropdownContent . setAttribute ( "role" , "menu" ) ;
513-
514- grouped [ groupName ] . forEach ( ( snippet ) => {
515- const item = document . createElement ( "button" ) ;
516- addClass ( item , "yasqe_snippetDropdownItem" ) ;
517- item . textContent = snippet . label ;
518- item . title = snippet . code ;
519- item . setAttribute ( "role" , "menuitem" ) ;
520- item . onclick = ( ) => {
521- this . insertSnippet ( snippet . code ) ;
522- dropdownContent . style . display = "none" ;
523- dropdownBtn . setAttribute ( "aria-expanded" , "false" ) ;
524- } ;
525- dropdownContent . appendChild ( item ) ;
524+ dropdownBtn . onclick = ( e ) => {
525+ e . stopPropagation ( ) ;
526+ const isOpen = dropdownContent . style . display === "block" ;
527+ // Close all other dropdowns
528+ const allDropdowns = this . snippetsBar ! . querySelectorAll ( ".yasqe_snippetDropdownContent" ) ;
529+ allDropdowns . forEach ( ( dd ) => {
530+ ( dd as HTMLElement ) . style . display = "none" ;
526531 } ) ;
532+ const allButtons = this . snippetsBar ! . querySelectorAll ( ".yasqe_snippetDropdownButton" ) ;
533+ allButtons . forEach ( ( btn ) => btn . setAttribute ( "aria-expanded" , "false" ) ) ;
527534
528- dropdownBtn . onclick = ( e ) => {
529- e . stopPropagation ( ) ;
530- const isOpen = dropdownContent . style . display === "block" ;
531- // Close all other dropdowns
532- const allDropdowns = this . snippetsBar ! . querySelectorAll ( ".yasqe_snippetDropdownContent" ) ;
533- allDropdowns . forEach ( ( dd ) => {
534- ( dd as HTMLElement ) . style . display = "none" ;
535- } ) ;
536- const allButtons = this . snippetsBar ! . querySelectorAll ( ".yasqe_snippetDropdownButton" ) ;
537- allButtons . forEach ( ( btn ) => btn . setAttribute ( "aria-expanded" , "false" ) ) ;
535+ // Toggle this dropdown
536+ if ( ! isOpen ) {
537+ dropdownContent . style . display = "block" ;
538+ dropdownBtn . setAttribute ( "aria-expanded" , "true" ) ;
539+ }
540+ } ;
538541
539- // Toggle this dropdown
540- if ( ! isOpen ) {
541- dropdownContent . style . display = "block" ;
542- dropdownBtn . setAttribute ( "aria-expanded" , "true" ) ;
543- }
544- } ;
542+ dropdown . appendChild ( dropdownBtn ) ;
543+ dropdown . appendChild ( dropdownContent ) ;
544+ allItems . push ( dropdown ) ;
545+ } ) ;
546+
547+ // Add ungrouped snippets as individual buttons
548+ ungrouped . forEach ( ( snippet ) => {
549+ const btn = document . createElement ( "button" ) ;
550+ addClass ( btn , "yasqe_snippetButton" ) ;
551+ btn . textContent = snippet . label ;
552+ btn . title = snippet . code ;
553+ btn . setAttribute ( "aria-label" , `Insert ${ snippet . label } snippet` ) ;
554+ btn . onclick = ( ) => this . insertSnippet ( snippet . code ) ;
555+ allItems . push ( btn ) ;
556+ } ) ;
545557
546- dropdown . appendChild ( dropdownBtn ) ;
547- dropdown . appendChild ( dropdownContent ) ;
548- this . snippetsBar ! . appendChild ( dropdown ) ;
558+ // Add all items to visible container initially
559+ allItems . forEach ( ( item ) => visibleContainer . appendChild ( item ) ) ;
560+ this . snippetsBar . appendChild ( visibleContainer ) ;
561+
562+ // Create "Show More" dropdown (initially hidden)
563+ const showMoreDropdown = document . createElement ( "div" ) ;
564+ addClass ( showMoreDropdown , "yasqe_snippetDropdown" , "yasqe_showMore" ) ;
565+ showMoreDropdown . style . display = "none" ;
566+
567+ const showMoreBtn = document . createElement ( "button" ) ;
568+ addClass ( showMoreBtn , "yasqe_snippetDropdownButton" , "yasqe_showMoreButton" ) ;
569+ showMoreBtn . textContent = "More " ;
570+ const chevron = drawSvgStringAsElement ( imgs . chevronDown ) ;
571+ addClass ( chevron , "chevronIcon" ) ;
572+ showMoreBtn . appendChild ( chevron ) ;
573+ showMoreBtn . setAttribute ( "aria-label" , "Show more snippets" ) ;
574+ showMoreBtn . setAttribute ( "aria-expanded" , "false" ) ;
575+
576+ const showMoreContent = document . createElement ( "div" ) ;
577+ addClass ( showMoreContent , "yasqe_snippetDropdownContent" , "yasqe_showMoreContent" ) ;
578+ showMoreContent . setAttribute ( "role" , "menu" ) ;
579+
580+ showMoreBtn . onclick = ( e ) => {
581+ e . stopPropagation ( ) ;
582+ const isOpen = showMoreContent . style . display === "block" ;
583+ // Close all other dropdowns
584+ const allDropdowns = this . snippetsBar ! . querySelectorAll ( ".yasqe_snippetDropdownContent" ) ;
585+ allDropdowns . forEach ( ( dd ) => {
586+ if ( dd !== showMoreContent ) {
587+ ( dd as HTMLElement ) . style . display = "none" ;
588+ }
589+ } ) ;
590+ const allButtons = this . snippetsBar ! . querySelectorAll ( ".yasqe_snippetDropdownButton" ) ;
591+ allButtons . forEach ( ( btn ) => {
592+ if ( btn !== showMoreBtn ) {
593+ btn . setAttribute ( "aria-expanded" , "false" ) ;
594+ }
549595 } ) ;
550596
551- // Add ungrouped snippets as individual buttons
552- ungrouped . forEach ( ( snippet ) => {
553- const btn = document . createElement ( "button" ) ;
554- addClass ( btn , "yasqe_snippetButton" ) ;
555- btn . textContent = snippet . label ;
556- btn . title = snippet . code ;
557- btn . setAttribute ( "aria-label" , `Insert ${ snippet . label } snippet` ) ;
558- btn . onclick = ( ) => this . insertSnippet ( snippet . code ) ;
559- this . snippetsBar ! . appendChild ( btn ) ;
597+ // Toggle this dropdown
598+ if ( ! isOpen ) {
599+ showMoreContent . style . display = "block" ;
600+ showMoreBtn . setAttribute ( "aria-expanded" , "true" ) ;
601+ } else {
602+ showMoreContent . style . display = "none" ;
603+ showMoreBtn . setAttribute ( "aria-expanded" , "false" ) ;
604+ }
605+ } ;
606+
607+ showMoreDropdown . appendChild ( showMoreBtn ) ;
608+ showMoreDropdown . appendChild ( showMoreContent ) ;
609+ this . snippetsBar . appendChild ( showMoreDropdown ) ;
610+
611+ // Create function to handle overflow detection
612+ const handleOverflow = ( ) => {
613+ if ( ! this . snippetsBar ) return ;
614+
615+ const containerWidth = this . snippetsBar . offsetWidth ;
616+ const showMoreWidth = 80 ; // Approximate width for "Show More" button
617+ let availableWidth = containerWidth - showMoreWidth - 20 ; // 20px for padding/margin
618+ let currentWidth = 0 ;
619+ const overflowItems : { element : HTMLElement ; snippet ?: Snippet ; groupName ?: string } [ ] = [ ] ;
620+
621+ // Check each item to see if it fits
622+ allItems . forEach ( ( item , index ) => {
623+ const itemWidth = item . offsetWidth + 5 ; // 5px gap
624+
625+ if ( currentWidth + itemWidth > availableWidth && index > 0 ) {
626+ // This item overflows, hide it and add to show more
627+ item . style . display = "none" ;
628+
629+ // Find the corresponding snippet or group
630+ if ( item . classList . contains ( "yasqe_snippetDropdown" ) ) {
631+ // It's a group dropdown
632+ const groupBtn = item . querySelector ( ".yasqe_snippetDropdownButton" ) ;
633+ const groupName = groupBtn ?. textContent ?. trim ( ) . replace ( " " , "" ) || "Group" ;
634+ overflowItems . push ( { element : item , groupName } ) ;
635+ } else {
636+ // It's an individual button
637+ const snippet = ungrouped . find ( ( s ) => s . label === item . textContent ) ;
638+ overflowItems . push ( { element : item , snippet } ) ;
639+ }
640+ } else {
641+ currentWidth += itemWidth ;
642+ }
560643 } ) ;
644+
645+ // If there are overflow items, show the "Show More" button
646+ if ( overflowItems . length > 0 ) {
647+ showMoreDropdown . style . display = "inline-block" ;
648+
649+ // Populate show more content with proper grouping
650+ overflowItems . forEach ( ( { element, snippet, groupName } ) => {
651+ if ( groupName ) {
652+ // It's a group - add a group header and its items
653+ const groupHeader = document . createElement ( "div" ) ;
654+ addClass ( groupHeader , "yasqe_snippetGroupHeader" ) ;
655+ groupHeader . textContent = groupName ;
656+ showMoreContent . appendChild ( groupHeader ) ;
657+
658+ const groupItems = element . querySelectorAll ( ".yasqe_snippetDropdownItem" ) ;
659+ groupItems . forEach ( ( item ) => {
660+ const clonedItem = item . cloneNode ( true ) as HTMLElement ;
661+ clonedItem . onclick = ( item as HTMLElement ) . onclick ;
662+ showMoreContent . appendChild ( clonedItem ) ;
663+ } ) ;
664+ } else if ( snippet ) {
665+ // It's an individual snippet
666+ const item = document . createElement ( "button" ) ;
667+ addClass ( item , "yasqe_snippetDropdownItem" ) ;
668+ item . textContent = snippet . label ;
669+ item . title = snippet . code ;
670+ item . setAttribute ( "role" , "menuitem" ) ;
671+ item . onclick = ( ) => {
672+ this . insertSnippet ( snippet . code ) ;
673+ showMoreContent . style . display = "none" ;
674+ showMoreBtn . setAttribute ( "aria-expanded" , "false" ) ;
675+ } ;
676+ showMoreContent . appendChild ( item ) ;
677+ }
678+ } ) ;
679+ }
680+ } ;
681+
682+ // Run overflow detection on next frame
683+ requestAnimationFrame ( handleOverflow ) ;
684+
685+ // Set up resize handler for overflow detection
686+ // Remove any existing handler first
687+ if ( this . snippetsResizeHandler ) {
688+ window . removeEventListener ( "resize" , this . snippetsResizeHandler ) ;
561689 }
562690
691+ // Create and store the resize handler
692+ this . snippetsResizeHandler = ( ) => {
693+ if ( ! this . snippetsBar ) return ;
694+
695+ // Reset all items to visible first
696+ allItems . forEach ( ( item ) => ( item . style . display = "" ) ) ;
697+ showMoreDropdown . style . display = "none" ;
698+ showMoreContent . innerHTML = "" ;
699+
700+ // Re-run overflow detection
701+ requestAnimationFrame ( handleOverflow ) ;
702+ } ;
703+
704+ // Add the resize handler
705+ window . addEventListener ( "resize" , this . snippetsResizeHandler ) ;
706+
563707 // Set up click handler for closing dropdowns when clicking outside
564708 // Remove any existing handler first
565709 if ( this . snippetsClickHandler ) {
0 commit comments