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(useTitle): restore title on unmounted #3570

Merged
merged 9 commits into from
Dec 5, 2023
76 changes: 51 additions & 25 deletions packages/core/useTitle/index.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,44 @@
import type { MaybeRef, MaybeRefOrGetter, ReadonlyRefOrGetter } from '@vueuse/shared'
import { toRef, toValue } from '@vueuse/shared'
import type { Fn, MaybeRef, MaybeRefOrGetter, ReadonlyRefOrGetter } from '@vueuse/shared'
import { toRef, toValue, tryOnBeforeUnmount } from '@vueuse/shared'
import type { ComputedRef, Ref } from 'vue-demi'
import { watch } from 'vue-demi'
import { useMutationObserver } from '../useMutationObserver'
import type { ConfigurableDocument } from '../_configurable'
import { defaultDocument } from '../_configurable'

export type UseTitleOptionsBase =
{
export type UseTitleOptionsBase = {
/**
* Observe `document.title` changes using MutationObserve
* Cannot be used together with `titleTemplate` option.
*
* @default false
* Stop watching the title when unmounted
* @default true
*/
observe?: boolean
}
| {
stopOnUnmount?: boolean
Doctor-wu marked this conversation as resolved.
Show resolved Hide resolved
Doctor-wu marked this conversation as resolved.
Show resolved Hide resolved

/**
* The template string to parse the title (e.g., '%s | My Website')
* Cannot be used together with `observe` option.
*
* @default '%s'
* Restore the original title when unmounted
* @param originTitle original title
* @returns restored title
*/
titleTemplate?: MaybeRef<string> | ((title: string) => string)
}
restoreOnUnmount?: (originTitle: string) => string
Doctor-wu marked this conversation as resolved.
Show resolved Hide resolved
} & (
{
/**
* Observe `document.title` changes using MutationObserve
* Cannot be used together with `titleTemplate` option.
*
* @default false
*/
observe?: boolean
}
| {
/**
* The template string to parse the title (e.g., '%s | My Website')
* Cannot be used together with `observe` option.
*
* @default '%s'
*/
titleTemplate?: MaybeRef<string> | ((title: string) => string)
}
)

export type UseTitleOptions = ConfigurableDocument & UseTitleOptionsBase

Expand Down Expand Up @@ -56,7 +70,11 @@ export function useTitle(
*/
const {
document = defaultDocument,
restoreOnUnmount = t => t,
stopOnUnmount = true,
} = options
const originalTitle = document?.title ?? ''
const stops: (Fn)[] = []

const title: Ref<string | null | undefined> = toRef(newTitle ?? document?.title ?? null)
const isReadonly = newTitle && typeof newTitle === 'function'
Expand All @@ -70,26 +88,34 @@ export function useTitle(
: toValue(template).replace(/%s/g, t)
}

watch(
title,
(t, o) => {
if (t !== o && document)
document.title = format(typeof t === 'string' ? t : '')
},
{ immediate: true },
stops.push(
watch(
title,
(t, o) => {
if (t !== o && document)
document.title = format(typeof t === 'string' ? t : '')
},
{ immediate: true },
),
)

if ((options as any).observe && !(options as any).titleTemplate && document && !isReadonly) {
useMutationObserver(
const observer = useMutationObserver(
document.head?.querySelector('title'),
() => {
if (document && document.title !== title.value)
title.value = format(document.title)
},
{ childList: true },
)
stops.push(observer.stop)
}

tryOnBeforeUnmount(() => {
title.value = restoreOnUnmount(originalTitle)
stopOnUnmount && stops.forEach(s => s())
})

return title
}

Expand Down