Created
September 27, 2023 07:34
-
-
Save intrnl/74db109ba4f79181ec6a7f8df156fc7b to your computer and use it in GitHub Desktop.
Solid.js SPA meta title provider
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
createContext, | |
createEffect, | |
createMemo, | |
createSignal, | |
onCleanup, | |
onMount, | |
useContext, | |
} from 'solid-js'; | |
import type { JSX } from 'solid-js/jsx-runtime'; | |
type TitleNode = Comment & { _render: () => string }; | |
interface MetaContextValue { | |
title(node: TitleNode): void; | |
} | |
const MetaContext = createContext<MetaContextValue>(); | |
export interface MetaProviderProps { | |
children?: JSX.Element; | |
} | |
export const MetaProvider = (props: MetaProviderProps) => { | |
const [titles, setTitles] = createSignal<TitleNode[]>([]); | |
const activeTitle = createMemo(() => { | |
const $titles = titles(); | |
return $titles.length > 0 ? $titles[0] : undefined; | |
}); | |
createEffect(() => { | |
const $activeTitle = activeTitle(); | |
if ($activeTitle) { | |
createEffect(() => { | |
const result = $activeTitle._render(); | |
document.title = result; | |
}); | |
} | |
}); | |
const context: MetaContextValue = { | |
title(node) { | |
// 1. Push our new node into the array, sort them according to priority | |
{ | |
const next = titles().concat(node); | |
setTitles(next.sort(collateNode)); | |
} | |
// 2. Add a cleanup that would remove our node on unmount | |
onCleanup(() => { | |
const next = titles().slice(); | |
next.splice(next.indexOf(node), 1); | |
setTitles(next); | |
}); | |
}, | |
}; | |
return <MetaContext.Provider value={context}>{props.children}</MetaContext.Provider>; | |
}; | |
export interface TitleProps { | |
template?: boolean; | |
render: string | (() => string); | |
} | |
export const Title = (props: TitleProps) => { | |
const context = useContext(MetaContext); | |
const node = document.createComment('!solid-title') as TitleNode; | |
node._render = () => { | |
const $render = props.render; | |
return typeof $render === 'function' ? $render() : $render; | |
}; | |
onMount(() => { | |
context!.title(node); | |
}); | |
return node; | |
}; | |
// Node document sort | |
const FOLLOWING = 4; | |
const CONTAINED_BY = 16; | |
const PRECEDING = 2; | |
const CONTAINS = 8; | |
const collateNode = (a: Node, b: Node) => { | |
const position = a.compareDocumentPosition(b); | |
if (position & (FOLLOWING | CONTAINED_BY)) { | |
return 1; | |
} | |
if (position & (PRECEDING | CONTAINS)) { | |
return -1; | |
} | |
return 0; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment