@@ -21,13 +21,16 @@ const {
2121} = usePackageListPreferences ()
2222
2323// Debounced URL update for page (less aggressive to avoid too many URL changes)
24+ // Use History API directly to update URL without triggering Router's scroll-to-top
2425const updateUrlPage = debounce ((page : number ) => {
25- router .replace ({
26- query: {
27- ... route .query ,
28- page: page > 1 ? page : undefined ,
29- },
30- })
26+ const url = new URL (window .location .href )
27+ if (page > 1 ) {
28+ url .searchParams .set (' page' , page .toString ())
29+ } else {
30+ url .searchParams .delete (' page' )
31+ }
32+ // This updates the address bar "silently"
33+ window .history .replaceState ({}, ' ' , url )
3134}, 500 )
3235
3336const { model : searchQuery, provider : searchProvider } = useGlobalSearch ()
@@ -276,7 +279,8 @@ function handlePageChange(page: number) {
276279}
277280
278281// Reset page when query changes
279- watch (query , () => {
282+ watch (query , (newQuery , oldQuery ) => {
283+ if (newQuery .trim () === (oldQuery || ' ' ).trim ()) return
280284 currentPage .value = 1
281285 hasInteracted .value = true
282286})
@@ -536,7 +540,7 @@ defineOgImageComponent('Default', {
536540 </script >
537541
538542<template >
539- <main class =" flex-1 py-8" :class =" { 'overflow-x-hidden': viewMode !== 'table' }" >
543+ <main class =" flex-1 py-8 search-page " :class =" { 'overflow-x-hidden': viewMode !== 'table' }" >
540544 <div class =" container-sm" >
541545 <div class =" flex items-center justify-between gap-4 mb-4" >
542546 <h1 class =" font-mono text-2xl sm:text-3xl font-medium" >
@@ -545,12 +549,13 @@ defineOgImageComponent('Default', {
545549 <SearchProviderToggle />
546550 </div >
547551
548- <section v-if =" query" >
549- <!-- Initial loading (only after user interaction, not during view transition) -->
550- <LoadingSpinner v-if =" showSearching" :text =" $t('search.searching')" />
552+ <section v-if =" query" class =" results-layout" >
553+ <LoadingSpinner
554+ v-if =" showSearching && displayResults.length === 0"
555+ :text =" $t('search.searching')"
556+ />
551557
552- <div v-else-if =" visibleResults" >
553- <!-- User/Org search suggestions -->
558+ <div v-show =" results || displayResults.length > 0" >
554559 <div v-if =" validatedSuggestions.length > 0" class =" mb-6 space-y-3" >
555560 <SearchSuggestionCard
556561 v-for =" (suggestion, idx) in validatedSuggestions"
@@ -565,9 +570,8 @@ defineOgImageComponent('Default', {
565570 />
566571 </div >
567572
568- <!-- Claim prompt - shown at top when valid name but no exact match -->
569573 <div
570- v-if =" showClaimPrompt && visibleResults.total > 0"
574+ v-if =" showClaimPrompt && visibleResults && visibleResults .total > 0"
571575 class =" mb-6 p-4 bg-bg-subtle border border-border rounded-lg sm:flex hidden flex-row sm:items-center gap-3 sm:gap-4"
572576 >
573577 <div class =" flex-1 min-w-0" >
@@ -585,15 +589,13 @@ defineOgImageComponent('Default', {
585589 </button >
586590 </div >
587591
588- <!-- Rate limited by npm - check FIRST before showing any results -->
589592 <div v-if =" isRateLimited" role =" status" class =" py-12" >
590593 <p class =" text-fg-muted font-mono mb-6 text-center" >
591594 {{ $t('search.rate_limited') }}
592595 </p >
593596 </div >
594597
595- <!-- Enhanced toolbar -->
596- <div v-else-if =" visibleResults.total > 0" class =" mb-6" >
598+ <div v-else-if =" visibleResults && visibleResults.total > 0" class =" mb-6" >
597599 <PackageListToolbar
598600 :filters =" filters"
599601 v-model:sort-option =" sortOption"
@@ -618,7 +620,6 @@ defineOgImageComponent('Default', {
618620 @update:updated-within =" setUpdatedWithin"
619621 @toggle-keyword =" toggleKeyword"
620622 />
621- <!-- Show count status (infinite scroll mode only) -->
622623 <p
623624 v-if =" viewMode === 'cards' && paginationMode === 'infinite'"
624625 role =" status"
@@ -642,7 +643,6 @@ defineOgImageComponent('Default', {
642643 $t('search.updating')
643644 }}</span >
644645 </p >
645- <!-- Show "x of y" (paginated/table mode only) -->
646646 <p
647647 v-if =" viewMode === 'table' || paginationMode === 'paginated'"
648648 role =" status"
@@ -664,13 +664,11 @@ defineOgImageComponent('Default', {
664664 </p >
665665 </div >
666666
667- <!-- No results found -->
668667 <div v-else-if =" status === 'success' || status === 'error'" role =" status" class =" py-12" >
669668 <p class =" text-fg-muted font-mono mb-6 text-center" >
670669 {{ $t('search.no_results', { query }) }}
671670 </p >
672671
673- <!-- User/Org suggestions when no packages found -->
674672 <div v-if =" validatedSuggestions.length > 0" class =" max-w-md mx-auto mb-6 space-y-3" >
675673 <SearchSuggestionCard
676674 v-for =" (suggestion, idx) in validatedSuggestions"
@@ -685,7 +683,6 @@ defineOgImageComponent('Default', {
685683 />
686684 </div >
687685
688- <!-- Offer to claim the package name if it's valid -->
689686 <div v-if =" showClaimPrompt" class =" max-w-md mx-auto text-center hidden sm:block" >
690687 <div class =" p-4 bg-bg-subtle border border-border rounded-lg" >
691688 <p class =" text-sm text-fg-muted mb-3" >{{ $t('search.want_to_claim') }}</p >
@@ -701,7 +698,7 @@ defineOgImageComponent('Default', {
701698 </div >
702699
703700 <PackageList
704- v-if =" displayResults.length > 0 && !isRateLimited"
701+ v-show =" displayResults.length > 0 && !isRateLimited"
705702 :results =" displayResults"
706703 :search-query =" query"
707704 :filters =" filters"
@@ -722,7 +719,6 @@ defineOgImageComponent('Default', {
722719 @click-keyword =" toggleKeyword"
723720 />
724721
725- <!-- Pagination controls -->
726722 <PaginationControls
727723 v-if =" displayResults.length > 0 && !isRateLimited"
728724 v-model:mode =" paginationMode"
@@ -739,7 +735,6 @@ defineOgImageComponent('Default', {
739735 </section >
740736 </div >
741737
742- <!-- Claim package modal -->
743738 <PackageClaimPackageModal
744739 ref =" claimPackageModalRef"
745740 :package-name =" query"
@@ -748,3 +743,13 @@ defineOgImageComponent('Default', {
748743 />
749744 </main >
750745</template >
746+
747+ <style scoped>
748+ .search-page {
749+ overflow-anchor : none ;
750+ }
751+
752+ .results-layout {
753+ min-height : 100vh ;
754+ }
755+ </style >
0 commit comments