@pyreon/head 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +64 -0
- package/lib/analysis/index.js.html +5406 -0
- package/lib/index.js +276 -0
- package/lib/index.js.map +1 -0
- package/lib/types/index.d.ts +250 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index2.d.ts +131 -0
- package/lib/types/index2.d.ts.map +1 -0
- package/package.json +64 -0
- package/src/context.ts +108 -0
- package/src/dom.ts +117 -0
- package/src/index.ts +7 -0
- package/src/provider.tsx +35 -0
- package/src/ssr.ts +90 -0
- package/src/tests/head.test.ts +890 -0
- package/src/tests/setup.ts +3 -0
- package/src/use-head.ts +104 -0
package/src/use-head.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { onMount, onUnmount, useContext } from "@pyreon/core"
|
|
2
|
+
import { effect } from "@pyreon/reactivity"
|
|
3
|
+
import type { HeadEntry, HeadTag, UseHeadInput } from "./context"
|
|
4
|
+
import { HeadContext } from "./context"
|
|
5
|
+
import { syncDom } from "./dom"
|
|
6
|
+
|
|
7
|
+
function buildEntry(o: UseHeadInput): HeadEntry {
|
|
8
|
+
const tags: HeadTag[] = []
|
|
9
|
+
if (o.title != null) tags.push({ tag: "title", key: "title", children: o.title })
|
|
10
|
+
o.meta?.forEach((m, i) => {
|
|
11
|
+
tags.push({
|
|
12
|
+
tag: "meta",
|
|
13
|
+
key: m.name ?? m.property ?? `meta-${i}`,
|
|
14
|
+
props: m,
|
|
15
|
+
})
|
|
16
|
+
})
|
|
17
|
+
o.link?.forEach((l, i) => {
|
|
18
|
+
tags.push({
|
|
19
|
+
tag: "link",
|
|
20
|
+
key: l.href ? `link-${l.rel || ""}-${l.href}` : l.rel ? `link-${l.rel}` : `link-${i}`,
|
|
21
|
+
props: l,
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
o.script?.forEach((s, i) => {
|
|
25
|
+
const { children, ...rest } = s
|
|
26
|
+
tags.push({
|
|
27
|
+
tag: "script",
|
|
28
|
+
key: s.src ?? `script-${i}`,
|
|
29
|
+
props: rest as Record<string, string>,
|
|
30
|
+
...(children != null ? { children } : {}),
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
o.style?.forEach((s, i) => {
|
|
34
|
+
const { children, ...rest } = s
|
|
35
|
+
tags.push({
|
|
36
|
+
tag: "style",
|
|
37
|
+
key: `style-${i}`,
|
|
38
|
+
props: rest as Record<string, string>,
|
|
39
|
+
children,
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
o.noscript?.forEach((ns, i) => {
|
|
43
|
+
tags.push({ tag: "noscript", key: `noscript-${i}`, children: ns.children })
|
|
44
|
+
})
|
|
45
|
+
if (o.jsonLd) {
|
|
46
|
+
tags.push({
|
|
47
|
+
tag: "script",
|
|
48
|
+
key: "jsonld",
|
|
49
|
+
props: { type: "application/ld+json" },
|
|
50
|
+
children: JSON.stringify(o.jsonLd),
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
if (o.base) tags.push({ tag: "base", key: "base", props: o.base })
|
|
54
|
+
return {
|
|
55
|
+
tags,
|
|
56
|
+
titleTemplate: o.titleTemplate,
|
|
57
|
+
htmlAttrs: o.htmlAttrs,
|
|
58
|
+
bodyAttrs: o.bodyAttrs,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Register head tags (title, meta, link, script, style, noscript, base, jsonLd)
|
|
64
|
+
* for the current component.
|
|
65
|
+
*
|
|
66
|
+
* Accepts a static object or a reactive getter:
|
|
67
|
+
* useHead({ title: "My Page", meta: [{ name: "description", content: "..." }] })
|
|
68
|
+
* useHead(() => ({ title: `${count()} items` })) // updates when signal changes
|
|
69
|
+
*
|
|
70
|
+
* Tags are deduplicated by key — innermost component wins.
|
|
71
|
+
* Requires a <HeadProvider> (CSR) or renderWithHead() (SSR) ancestor.
|
|
72
|
+
*/
|
|
73
|
+
export function useHead(input: UseHeadInput | (() => UseHeadInput)): void {
|
|
74
|
+
const ctx = useContext(HeadContext)
|
|
75
|
+
if (!ctx) return // no HeadProvider — silently no-op
|
|
76
|
+
|
|
77
|
+
const id = Symbol()
|
|
78
|
+
|
|
79
|
+
if (typeof input === "function") {
|
|
80
|
+
if (typeof document !== "undefined") {
|
|
81
|
+
// CSR: reactive — re-register whenever signals change
|
|
82
|
+
effect(() => {
|
|
83
|
+
ctx.add(id, buildEntry(input()))
|
|
84
|
+
syncDom(ctx)
|
|
85
|
+
})
|
|
86
|
+
} else {
|
|
87
|
+
// SSR: evaluate once synchronously (no effects on server)
|
|
88
|
+
ctx.add(id, buildEntry(input()))
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
ctx.add(id, buildEntry(input))
|
|
92
|
+
// onMount is a no-op in SSR; syncDom has its own typeof document guard
|
|
93
|
+
onMount(() => {
|
|
94
|
+
syncDom(ctx)
|
|
95
|
+
return undefined
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
onUnmount(() => {
|
|
100
|
+
ctx.remove(id)
|
|
101
|
+
// syncDom has its own typeof document guard — no need to check here
|
|
102
|
+
syncDom(ctx)
|
|
103
|
+
})
|
|
104
|
+
}
|