Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nuxt): custom loading reset/hide delay + force finish() #25932

Merged
merged 12 commits into from
Mar 6, 2024
54 changes: 36 additions & 18 deletions packages/nuxt/src/app/composables/loading-indicator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ export type LoadingIndicatorOpts = {
duration: number
/** @default 200 */
throttle: number
/** @default 500 */
hideDelay: number
/** @default 400 */
resetDelay: number
/**
* You can provide a custom function to customize the progress estimation,
* which is a function that receives the duration of the loading bar (above)
Expand All @@ -15,22 +19,13 @@ export type LoadingIndicatorOpts = {
estimatedProgress?: (duration: number, elapsed: number) => number
}

function _hide (isLoading: Ref<boolean>, progress: Ref<number>) {
if (import.meta.client) {
setTimeout(() => {
isLoading.value = false
setTimeout(() => { progress.value = 0 }, 400)
}, 500)
}
}

export type LoadingIndicator = {
_cleanup: () => void
progress: Ref<number>
isLoading: Ref<boolean>
start: () => void
set: (value: number) => void
finish: () => void
finish: (opts: { force?: boolean }) => void
clear: () => void
}

Expand All @@ -40,15 +35,17 @@ function defaultEstimatedProgress (duration: number, elapsed: number): number {
}

function createLoadingIndicator (opts: Partial<LoadingIndicatorOpts> = {}) {
const { duration = 2000, throttle = 200 } = opts
const { duration = 2000, throttle = 200, hideDelay = 500, resetDelay = 400 } = opts
const getProgress = opts.estimatedProgress || defaultEstimatedProgress
const nuxtApp = useNuxtApp()
const progress = ref(0)
const isLoading = ref(false)
let done = false
let rafId: number

let _throttle: any = null
let throttleTimeout: number | NodeJS.Timeout
let hideTimeout: number | NodeJS.Timeout
let resetTimeout: number | NodeJS.Timeout

const start = () => set(0)

Expand All @@ -60,7 +57,7 @@ function createLoadingIndicator (opts: Partial<LoadingIndicatorOpts> = {}) {
clear()
progress.value = at < 0 ? 0 : at
if (throttle && import.meta.client) {
_throttle = setTimeout(() => {
throttleTimeout = setTimeout(() => {
isLoading.value = true
_startProgress()
}, throttle)
Expand All @@ -70,19 +67,40 @@ function createLoadingIndicator (opts: Partial<LoadingIndicatorOpts> = {}) {
}
}

function finish () {
function _hide () {
if (import.meta.client) {
hideTimeout = setTimeout(() => {
isLoading.value = false
resetTimeout = setTimeout(() => { progress.value = 0 }, resetDelay)
}, hideDelay)
}
}

function finish (opts: { force?: boolean } = {}) {
progress.value = 100
done = true
clear()
_hide(isLoading, progress)
_clearTimeouts()
if (opts.force) {
progress.value = 0
isLoading.value = false
} else {
_hide()
}
}

function _clearTimeouts () {
if (import.meta.client) {
clearTimeout(hideTimeout)
clearTimeout(resetTimeout)
}
}

function clear () {
clearTimeout(_throttle)
if (import.meta.client) {
clearTimeout(throttleTimeout)
cancelAnimationFrame(rafId)
}
_throttle = null
}

function _startProgress () {
Expand Down Expand Up @@ -113,7 +131,7 @@ function createLoadingIndicator (opts: Partial<LoadingIndicatorOpts> = {}) {
const unsubLoadingFinishHook = nuxtApp.hook('page:loading:end', () => {
finish()
})
const unsubError = nuxtApp.hook('vue:error', finish)
const unsubError = nuxtApp.hook('vue:error', () => finish())

_cleanup = () => {
unsubError()
Expand Down
15 changes: 15 additions & 0 deletions test/nuxt/composables.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,21 @@ describe('loading state', () => {
})
})

describe('loading state', () => {
it('expect loading state to be changed by force starting/stoping', async () => {
vi.stubGlobal('setTimeout', vi.fn((cb: Function) => cb()))
const nuxtApp = useNuxtApp()
const { isLoading, start, finish } = useLoadingIndicator()
expect(isLoading.value).toBeFalsy()
await nuxtApp.callHook('page:loading:start')
expect(isLoading.value).toBeTruthy()
start()
expect(isLoading.value).toBeTruthy()
finish()
expect(isLoading.value).toBeFalsy()
})
})

describe.skipIf(process.env.TEST_MANIFEST === 'manifest-off')('app manifests', () => {
it('getAppManifest', async () => {
const manifest = await getAppManifest()
Expand Down