@@ -555,6 +555,98 @@ defineOgImageComponent('Default', {
555555 : $t (' search.meta_description_packages' ),
556556 primaryColor: ' #60a5fa' ,
557557})
558+
559+ // -----------------------------------
560+ // Live region debouncing logic
561+ // -----------------------------------
562+ const isMobile = useIsMobile ()
563+
564+ // Evaluate the text that should be announced to screen readers
565+ const rawLiveRegionMessage = computed (() => {
566+ if (isRateLimited .value ) {
567+ return $t (' search.rate_limited' )
568+ }
569+
570+ // If status is pending, no update phrase needed yet
571+ if (status .value === ' pending' ) {
572+ return ' '
573+ }
574+
575+ if (visibleResults .value && displayResults .value .length > 0 ) {
576+ if (viewMode .value === ' table' || paginationMode .value === ' paginated' ) {
577+ const pSize =
578+ preferredPageSize .value === ' all'
579+ ? $n (effectiveTotal .value )
580+ : Math .min (preferredPageSize .value , effectiveTotal .value )
581+ return $t (
582+ ' filters.count.showing_paginated' ,
583+ {
584+ pageSize: pSize .toString (),
585+ count: $n (effectiveTotal .value ),
586+ },
587+ effectiveTotal .value ,
588+ )
589+ }
590+
591+ if (isRelevanceSort .value ) {
592+ return $t (
593+ ' search.found_packages' ,
594+ { count: $n (visibleResults .value .total ) },
595+ visibleResults .value .total ,
596+ )
597+ }
598+
599+ return $t (
600+ ' search.found_packages_sorted' ,
601+ { count: $n (effectiveTotal .value ) },
602+ effectiveTotal .value ,
603+ )
604+ }
605+
606+ if (status .value === ' success' || status .value === ' error' ) {
607+ if (displayResults .value .length === 0 && query .value ) {
608+ return $t (' search.no_results' , { query: query .value })
609+ }
610+ }
611+
612+ return ' '
613+ })
614+
615+ const debouncedLiveRegionMessage = ref (' ' )
616+
617+ const updateLiveRegionMobile = debounce ((val : string ) => {
618+ debouncedLiveRegionMessage .value = val
619+ }, 700 )
620+
621+ const updateLiveRegionDesktop = debounce ((val : string ) => {
622+ debouncedLiveRegionMessage .value = val
623+ }, 250 )
624+
625+ watch (
626+ rawLiveRegionMessage ,
627+ newVal => {
628+ if (! newVal ) {
629+ updateLiveRegionMobile .cancel ()
630+ updateLiveRegionDesktop .cancel ()
631+ debouncedLiveRegionMessage .value = ' '
632+ return
633+ }
634+
635+ if (isMobile .value ) {
636+ updateLiveRegionDesktop .cancel ()
637+ updateLiveRegionMobile (newVal )
638+ } else {
639+ updateLiveRegionMobile .cancel ()
640+ updateLiveRegionDesktop (newVal )
641+ }
642+ },
643+ { immediate: true },
644+ )
645+
646+ onBeforeUnmount (() => {
647+ updateLiveRegionMobile .cancel ()
648+ updateLiveRegionDesktop .cancel ()
649+ })
558650 </script >
559651
560652<template >
@@ -615,7 +707,7 @@ defineOgImageComponent('Default', {
615707 </button >
616708 </div >
617709
618- <div v-if =" isRateLimited" role = " status " class =" py-12" >
710+ <div v-if =" isRateLimited" class =" py-12" >
619711 <p class =" text-fg-muted font-mono mb-6 text-center" >
620712 {{ $t('search.rate_limited') }}
621713 </p >
@@ -648,7 +740,6 @@ defineOgImageComponent('Default', {
648740 />
649741 <p
650742 v-if =" viewMode === 'cards' && paginationMode === 'infinite'"
651- role =" status"
652743 class =" text-fg-muted text-sm mt-4 font-mono"
653744 >
654745 <template v-if =" isRelevanceSort " >
@@ -665,13 +756,12 @@ defineOgImageComponent('Default', {
665756 $t('search.found_packages_sorted', { count: $n(effectiveTotal) }, effectiveTotal)
666757 }}
667758 </template >
668- <span v-if =" status === 'pending'" class =" text-fg-subtle" >{{
669- $t('search.updating')
670- }} </span >
759+ <span aria-hidden = " true " v-if =" status === 'pending'" class =" text-fg-subtle" >
760+ {{ $t('search.updating') }}
761+ </span >
671762 </p >
672763 <p
673764 v-if =" viewMode === 'table' || paginationMode === 'paginated'"
674- role =" status"
675765 class =" text-fg-muted text-sm mt-4 font-mono"
676766 >
677767 {{
@@ -690,7 +780,7 @@ defineOgImageComponent('Default', {
690780 </p >
691781 </div >
692782
693- <div v-else-if =" status === 'success' || status === 'error'" role = " status " class =" py-12" >
783+ <div v-else-if =" status === 'success' || status === 'error'" class =" py-12" >
694784 <p class =" text-fg-muted font-mono mb-6 text-center" >
695785 {{ $t('search.no_results', { query }) }}
696786 </p >
@@ -767,6 +857,10 @@ defineOgImageComponent('Default', {
767857 :package-scope =" packageScope"
768858 :can-publish-to-scope =" canPublishToScope"
769859 />
860+
861+ <div role =" status" class =" sr-only" >
862+ {{ debouncedLiveRegionMessage }}
863+ </div >
770864 </main >
771865</template >
772866
0 commit comments