@@ -195,8 +195,10 @@ export default defineComponent({
195195
196196 // If there are menu items, select the first one for keyboard navigation
197197 if (menuItems .value .length > 0 ) {
198- // Find first non-divider item
199- const firstItemIndex = menuItems .value .findIndex ((item ) => ! item .divider );
198+ // Find first non-divider, non-disabled item
199+ const firstItemIndex = menuItems .value .findIndex (
200+ (item ) => ! item .divider && ! item .disabled
201+ );
200202 if (firstItemIndex !== - 1 ) {
201203 setActiveIndex (firstItemIndex );
202204 }
@@ -245,7 +247,14 @@ export default defineComponent({
245247 }
246248
247249 setActiveIndex (index );
248- selectMenuItem (name , id , subMenu , false , props .onSelection );
250+
251+ // For mouse clicks, always select the item and don't toggle submenus
252+ if (! subMenu ) {
253+ selectMenuItem (name , id , subMenu , false , props .onSelection );
254+ } else {
255+ // For submenu items, toggle the submenu
256+ toggleMenu (id , false );
257+ }
249258
250259 // Direct style manipulation to remove any focus borders
251260 const target = event .currentTarget as HTMLElement ;
@@ -293,29 +302,98 @@ export default defineComponent({
293302 switch (keyCode ) {
294303 case ' ArrowDown' :
295304 if (actvIndex < len - 1 ) {
296- const nextItemIsDivider = menuItems .value [actvIndex + 1 ]?.divider ;
305+ let nextIndex = actvIndex + 1 ;
306+
307+ // Skip disabled items and dividers
308+ while (
309+ nextIndex < len &&
310+ (menuItems .value [nextIndex ]?.divider || menuItems .value [nextIndex ]?.disabled )
311+ ) {
312+ nextIndex ++ ;
313+ }
297314
298- if (nextItemIsDivider ) {
299- setActiveIndex (actvIndex + 2 < len ? actvIndex + 2 : 0 );
300- } else {
301- setActiveIndex (actvIndex + 1 );
315+ // If we reached the end, loop back to the start
316+ if (nextIndex >= len ) {
317+ nextIndex = 0 ;
318+ // Skip disabled items and dividers at the beginning
319+ while (
320+ nextIndex < actvIndex &&
321+ (menuItems .value [nextIndex ]?.divider || menuItems .value [nextIndex ]?.disabled )
322+ ) {
323+ nextIndex ++ ;
324+ }
302325 }
326+
327+ setActiveIndex (nextIndex );
303328 } else if (actvIndex === len - 1 ) {
304- setActiveIndex (0 );
329+ // Find first non-disabled, non-divider item
330+ let nextIndex = 0 ;
331+ while (
332+ nextIndex < len &&
333+ (menuItems .value [nextIndex ]?.divider || menuItems .value [nextIndex ]?.disabled )
334+ ) {
335+ nextIndex ++ ;
336+ }
337+
338+ // If all items are disabled, keep current selection
339+ if (nextIndex >= len ) {
340+ nextIndex = actvIndex ;
341+ }
342+
343+ setActiveIndex (nextIndex );
305344 }
306345 break ;
307346
308347 case ' ArrowUp' :
309- const isDivider = menuItems .value [actvIndex - 1 ]?.divider ;
310- const nextIndex = isDivider
311- ? actvIndex - 2
312- : actvIndex - 1 < 0
313- ? len - 1
314- : actvIndex - 1 ;
315- setActiveIndex (nextIndex );
348+ if (actvIndex > 0 ) {
349+ let prevIndex = actvIndex - 1 ;
350+
351+ // Skip disabled items and dividers
352+ while (
353+ prevIndex >= 0 &&
354+ (menuItems .value [prevIndex ]?.divider || menuItems .value [prevIndex ]?.disabled )
355+ ) {
356+ prevIndex -- ;
357+ }
358+
359+ // If we reached the beginning, loop to the end
360+ if (prevIndex < 0 ) {
361+ prevIndex = len - 1 ;
362+ // Skip disabled items and dividers at the end
363+ while (
364+ prevIndex > actvIndex &&
365+ (menuItems .value [prevIndex ]?.divider || menuItems .value [prevIndex ]?.disabled )
366+ ) {
367+ prevIndex -- ;
368+ }
369+ }
370+
371+ setActiveIndex (prevIndex );
372+ } else {
373+ // Find last non-disabled, non-divider item
374+ let prevIndex = len - 1 ;
375+ while (
376+ prevIndex >= 0 &&
377+ (menuItems .value [prevIndex ]?.divider || menuItems .value [prevIndex ]?.disabled )
378+ ) {
379+ prevIndex -- ;
380+ }
381+
382+ // If all items are disabled, keep current selection
383+ if (prevIndex < 0 ) {
384+ prevIndex = actvIndex ;
385+ }
386+
387+ setActiveIndex (prevIndex );
388+ }
316389 break ;
317390
318391 case ' ArrowLeft' :
392+ // Skip disabled items
393+ if (item ?.disabled ) {
394+ break ;
395+ }
396+
319397 if (! props .flip ) {
320398 props .onClose (' ArrowLeft' );
321399 } else if (item .subMenu ) {
@@ -324,6 +402,11 @@ export default defineComponent({
324402 break ;
325403
326404 case ' ArrowRight' :
405+ // Skip disabled items
406+ if (item ?.disabled ) {
407+ break ;
408+ }
409+
327410 if (! props .flip && item ?.subMenu ) {
328411 toggleMenu (item .id || ' ' , true );
329412 // Focus handling for keyboard navigation
@@ -334,28 +417,28 @@ export default defineComponent({
334417 break ;
335418
336419 case ' Enter' :
337- if ( item ?. subMenu ) {
338- toggleMenu (item . id || ' ' , true );
339- // Focus handling for keyboard navigation
340- handleSubmenuOpen ( item . id || ' ' );
341- } else {
342- selectMenuItem (
343- item ?. name ,
344- item ?.id || ' ' ,
345- Boolean ( item ?.subMenu ) ,
346- false ,
347- props . onSelection
348- );
349-
350- // Announce selection for screen readers
351- if ( item ?. name ) {
352- const announcement = document . createElement ( ' div ' );
353- announcement . setAttribute ( ' aria-live ' , ' polite ' );
354- announcement .className = ' sr-only ' ;
355- announcement .textContent = ` Selected ${ item . name } ` ;
356- document . body . appendChild ( announcement ) ;
357- setTimeout (() => document .body .removeChild (announcement ), 1000 );
358- }
420+ // Skip disabled items for Enter key actions
421+ if (item ?. disabled ) {
422+ break ;
423+ }
424+
425+ // Only select regular menu items with Enter, submenus should only use arrow keys
426+ selectMenuItem (
427+ item ?.name ,
428+ item ?.id || ' ' ,
429+ Boolean ( item ?. subMenu ) ,
430+ false ,
431+ props . onSelection
432+ );
433+
434+ // Announce selection for screen readers
435+ if ( item ?. name ) {
436+ const announcement = document . createElement ( ' div ' );
437+ announcement .setAttribute ( ' aria-live ' , ' polite ' ) ;
438+ announcement .className = ' sr-only ' ;
439+ announcement . textContent = ` Selected ${ item . name } ` ;
440+ document .body .appendChild (announcement );
441+ setTimeout (() => document . body . removeChild ( announcement ), 1000 );
359442 }
360443 break ;
361444
@@ -407,9 +490,9 @@ export default defineComponent({
407490 // Find the submenu container
408491 const submenu = document .querySelector (` [data-submenu-id="${submenuId }"] ` ) as HTMLElement ;
409492 if (submenu ) {
410- // Find the first focusable item in the submenu
493+ // Find the first non-disabled focusable item in the submenu
411494 const firstItem = submenu .querySelector (
412- ' [role="menuitem"]:not([aria-disabled="true"])'
495+ ' [role="menuitem"]:not([aria-disabled="true"]):not(.divider) '
413496 ) as HTMLElement ;
414497 if (firstItem ) {
415498 firstItem .focus ();
@@ -479,10 +562,10 @@ export default defineComponent({
479562
480563 const handleAfterTransitionEnter = (el : Element ) => {
481564 try {
482- // Focus the first item in the submenu
565+ // Focus the first non-disabled item in the submenu
483566 nextTick (() => {
484567 const firstItem = el .querySelector (
485- ' [role="menuitem"]:not([aria-disabled="true"])'
568+ ' [role="menuitem"]:not([aria-disabled="true"]):not(.divider) '
486569 ) as HTMLElement ;
487570 if (firstItem ) {
488571 firstItem .focus ();
0 commit comments