Skip to content

vue-spark/router-plugins

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

41 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@vue-spark/router-plugins

Lightweight Vue Router 4 plugin collection - extend routing capabilities with minimal overhead.

Starting from v1.0.0, the plugin is developed based on vue-router-plugin-system (the old plugin registration method is deprecated). See its documentation for details.

中文文档

Online example

Installation

npm i @vue-spark/router-plugins

Plugin Registration

Method 1:

import ScrollerPlugin from '@vue-spark/router-plugins/scroller'

// initialize the plugin and install
ScrollerPlugin({
  selectors: {
    window: true,
    '.scrollable': true,
  },
}).install(router)

Method 2:

import ScrollerPlugin from '@vue-spark/router-plugins/scroller'

createApp(App)
  // register router first
  .use(router)
  // then register the plugin
  .use(
    ScrollerPlugin({
      selectors: {
        window: true,
        '.scrollable': true,
      },
    }),
  )

Method 3:

import ScrollerPlugin from '@vue-spark/router-plugins/scroller'
import { createWebHistory } from 'vue-router'
import { createRouter } from 'vue-router-plugin-system'

const router = createRouter({
  history: createWebHistory(),
  routes: [],
  plugins: [
    // initialize the plugin
    ScrollerPlugin({
      selectors: {
        window: true,
        '.scrollable': true,
      },
    }),
  ],
})

Plugin List

Supports passing and restoring state data during browser navigation (forward/backward) after caching pages with <KeepAlive>.

Usage Example
<!-- list.vue -->
<script
  setup
  lang="ts"
>
  import { shallowRef, onActivated } from 'vue'
  import { useRouter } from 'vue-router'

  interface Item {}

  const list = shallowRef<Item[]>([])
  const fetchList = async () => {
    fetch('/api/list').then((res) => {
      list.value = res.json()
    })
  }

  // Fetch immediately
  fetchList()

  const router = useRouter()
  onActivated(() => {
    // get() retrieves state data only, persists after refresh
    // const shouldRefresh = router.historyState.get('refreshList')

    // take() removes the data reference after retrieval, returns undefined on subsequent calls
    const shouldRefresh = router.historyState.take('refreshList')

    // Refetch list when needed
    if (shouldRefresh) {
      fetchList()
    }
  })
</script>

<template>
  <button @click="$router.push('/list/detail')">Add New</button>
  <ul>
    <li v-for="item in list">{{ item }}</li>
  </ul>
</template>
<!-- detail.vue -->
<script
  setup
  lang="ts"
>
  import { useRouter } from 'vue-router'

  const router = useRouter()
  function handleBack() {
    // Set memory-only historical state
    // router.setMemory({ refreshList: true })

    // Set deferred historical state after navigation
    router.setDeferred({ refreshList: true })

    router.back()
  }
</script>

<template>
  <button @click="handleBack">Back</button>
</template>

Type Definitions

interface HistoryStateManager {
  /**
   * Equivalent to `router.options.history.state`
   */
  readonly raw: VueRouter.HistoryState
  /**
   * Namespace name of the state
   */
  readonly namespace: string

  /**
   * Sets state data, synchronizes to `router.options.history.state` immediately
   * Only supports shallow copy
   */
  set: {
    <T extends {}>(state: Partial<T>): void
    <T = unknown>(key: string, value: T | undefined): T | undefined
  }

  /***
   * Sets memory-only state data, won't update `router.options.history.state`
   */
  setMemory: HistoryStateManager['set']

  /**
   * Defers state setting, synchronizes to `router.options.history.state` on next successful navigation
   * Can be called multiple times before next navigation, deferred states are buffered
   * Buffer resets on both successful and failed navigation
   */
  setDeferred: HistoryStateManager['set']
  /**
   * Cancels deferred state settings, resets buffer immediately
   */
  cancelDeferred: () => void
  /**
   * Synchronizes buffered state to `router.options.history.state` immediately
   */
  applyDeferred: () => void

  /**
   * Retrieves state data
   */
  get: {
    <T extends {}>(): Partial<T>
    <T = unknown>(key: string): T | undefined
  }

  /**
   * Retrieves and removes state data
   */
  take: {
    <T extends {}>(): Partial<T>
    <T = unknown>(key: string): T | undefined
  }

  /**
   * Destroys state data in current namespace
   */
  destroy: () => void
}

interface Router {
  historyState: HistoryStateManager & ((namespace: string) => HistoryStateManager)
}

Simulates mobile navigation direction (forward/backward/refresh) for animation and cache control.

Usage Example
<!-- App.vue -->
<script
  setup
  lang="ts"
>
  import type { ResolveViewKey } from 'vue-router-better-view'
  import { ref, shallowReactive } from 'vue'
  import { useRouter } from 'vue-router'

  const router = useRouter()
  const transitionName = ref<string>()
  const keepAliveValues = shallowReactive(new Set<string>())

  const resolveViewKey: ResolveViewKey = (route) => {
    return route.meta.title ? route.fullPath : null
  }

  router.navigationDirection.listen((direction, to, from) => {
    switch (direction) {
      case 'forward': {
        transitionName.value = 'page-in'
        keepAliveValues.add(to.fullPath)
        break
      }
      case 'backward': {
        transitionName.value = 'page-out'
        keepAliveValues.delete(from.fullPath)
        break
      }
      default: {
        transitionName.value = undefined
        keepAliveValues.delete(from.fullPath)
        keepAliveValues.add(to.fullPath)
        break
      }
    }
  })
</script>

<template>
  <BetterRouterView
    v-slot="{ Component: viewComponent, route }"
    :resolve-view-key
  >
    <Transition
      :name="transitionName"
      :css="!!transitionName"
    >
      <KeepAlive :include="[...keepAliveValues]">
        <Component
          :is="viewComponent"
          :key="route.fullPath"
        />
      </KeepAlive>
    </Transition>
  </BetterRouterView>
</template>

<style scoped>
  .page-in-enter-active,
  .page-in-leave-active,
  .page-out-enter-active,
  .page-out-leave-active {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    background-color: #fff;
    overflow: hidden;
    will-change: transform;
    transition:
      transform 0.3s linear,
      /* fix: 避免离场元素闪烁 */ opacity 0.31s;
  }

  .page-in-leave-to {
    /* fix: 避免离场元素闪烁 */
    opacity: 1;
  }
  .page-in-enter-from {
    z-index: 2;
    transform: translateX(100%);
  }
  .page-in-enter-to {
    z-index: 2;
  }

  .page-out-leave-from {
    z-index: 2;
  }
  .page-out-leave-to {
    z-index: 2;
    transform: translateX(100%);
  }
</style>

MemoryHistory Support: When using createMemoryHistory(), since MemoryHistory doesn't provide browser history delta information, the plugin will identify all navigation operations (including push and replace) as forward direction. If you need to customize direction recognition logic in MemoryHistory environment, you can use the directionResolver option:

const router = createRouter({
  history: createMemoryHistory(),
  routes,
  plugins: [
    NavigationDirectionPlugin({
      directionResolver: ({ to, from, delta }) => {
        // Custom logic: determine direction based on route changes
        if (to.path !== from.path) {
          // You can determine forward/backward/replace based on business logic
          return NavigationDirection.unchanged // or other logic
        }
        return delta > 0 ? NavigationDirection.forward : NavigationDirection.backward
      },
    }),
  ],
})

Configuration Options

interface NavigationDirectionOptions {
  /**
   * Navigation direction resolver
   */
  directionResolver?: NavigationDirectionResolver
}

Type Definitions

enum NavigationDirection {
  forward = 'forward',
  backward = 'backward',
  unchanged = 'unchanged',
}

interface INavigationDirection {
  /**
   * Current navigation direction (last navigation)
   */
  currentDirection: ShallowRef<NavigationDirection>
  /**
   * Sets next navigation direction, takes effect on next successful navigation
   * Needs re-setting after failed navigation
   */
  setNextDirection: (direction: NavigationDirection) => void
  /**
   * Listens for navigation direction changes, callback is automatically removed on scope disposal
   */
  listen: (callback: NavigationDirectionCallback) => () => void
}

interface Router {
  navigationDirection: INavigationDirection
}

Automatically saves and restores scroll position for long pages or list pages.

Usage Example
// router/index.ts
import ScrollerPlugin from '@vue-spark/router-plugins/scroller'
import { createWebHistory } from 'vue-router'
import { createRouter } from 'vue-router-plugin-system'

const router = createRouter({
  history: createWebHistory(),
  routes: [],
  plugins: [
    ScrollerPlugin({
      // set the selectors you want to use
      selectors: {
        window: true,
        '.scrollable': true,
      },
    }),
  ],
})

When using with <Transition>, manually trigger scroll restoration via router.scroller.trigger() in after-enter event:

<template>
  <RouterView v-slot="{ Component: viewComponent }">
    <Transition @after-enter="$router.scroller.trigger()">
      <Component :is="viewComponent" />
    </Transition>
  </RouterView>
</template>

Configuration Options

interface ScrollerOptions {
  /**
   * Scroll element selectors, supports special selector `window`
   */
  selectors: Record<string, boolean | ScrollHandler>
  /**
   * Scroll behavior
   */
  behavior?: ScrollBehavior
  /**
   * Restore scroll position only when navigating backward (suitable for mobile)
   *
   * **Note: Requires NavigationDirectionPlugin**
   */
  scrollOnlyBackward?: boolean
}

Type Definitions

interface ScrollPositionCoordinates {
  left?: number
  top?: number
}

type ScrollPositionCoordinatesGroup = Record<string, ScrollPositionCoordinates>

interface Scroller {
  /**
   * Scroll position records
   */
  positionsMap: ShallowReactive<Map<string, ScrollPositionCoordinatesGroup>>
  /**
   * Manually trigger scroll restoration for current route
   * Useful after Transition animations
   */
  trigger: () => void
}

interface Router {
  scroller: Scroller
}

Detects current navigation state for transition animations or loading indicators.

Usage Example
<template>
  <div :class="{ 'is-navigating': $router.isNavigating.value }">
    <RouterView />
  </div>
</template>

Type Definitions

interface Router {
  isNavigating: ShallowRef<boolean>
}

Records previous route information for source-based logic decisions.

Usage Example
const router = createRouter({...})

const originalBack = router.back
router.back = () => {
  const currentRoute = router.currentRoute.value
  const previousRoute = router.previousRoute.value
  // Return to TabBar page when previous route is root and current is not TabBar
  if (previousRoute && previousRoute.fullPath === '/' && !currentRoute.meta.isTabBar) {
    // Set next navigation direction for animation
    router.navigationDirection.setNextDirection(NavigationDirection.backward)
    router.replace('/tab-bar')
    return
  }
  originalBack()
}

Type Definitions

interface PreviousRoute
  extends Readonly<
    Pick<VueRouter.RouteLocationNormalizedLoaded, 'name' | 'path' | 'fullPath' | 'hash'>
  > {}

interface Router {
  previousRoute: ShallowRef<PreviousRoute | undefined>
}

Migration Guide

From v0.x to v1.x

  • Remove all plugin registration shorthands, only support on-demand importing required plugins.
  • Plugins are developed based on vue-router-plugin-system (old plugin registration method is deprecated). See its documentation for details.
  • Single plugin import path changed to @vue-spark/router-plugins/[plugin-name].
  • selectors configuration item in ScrollerPlugin has no default value and is now required.

About

Lightweight Vue Router 4 plugin collection - extend routing capabilities with minimal overhead.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages