@pyreon/head 0.5.6 → 0.6.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.
@@ -1,90 +1,31 @@
1
- import { createContext, h, pushContext } from "@pyreon/core";
2
- import { renderToString } from "@pyreon/runtime-server";
1
+ import { VNode } from "@pyreon/core";
3
2
 
4
- //#region src/context.ts
5
- function createHeadContext() {
6
- const map = /* @__PURE__ */new Map();
7
- let dirty = true;
8
- let cachedTags = [];
9
- let cachedTitleTemplate;
10
- let cachedHtmlAttrs = {};
11
- let cachedBodyAttrs = {};
12
- function rebuild() {
13
- if (!dirty) return;
14
- dirty = false;
15
- const keyed = /* @__PURE__ */new Map();
16
- const unkeyed = [];
17
- let titleTemplate;
18
- const htmlAttrs = {};
19
- const bodyAttrs = {};
20
- for (const entry of map.values()) {
21
- for (const tag of entry.tags) if (tag.key) keyed.set(tag.key, tag);else unkeyed.push(tag);
22
- if (entry.titleTemplate !== void 0) titleTemplate = entry.titleTemplate;
23
- if (entry.htmlAttrs) Object.assign(htmlAttrs, entry.htmlAttrs);
24
- if (entry.bodyAttrs) Object.assign(bodyAttrs, entry.bodyAttrs);
25
- }
26
- cachedTags = [...keyed.values(), ...unkeyed];
27
- cachedTitleTemplate = titleTemplate;
28
- cachedHtmlAttrs = htmlAttrs;
29
- cachedBodyAttrs = bodyAttrs;
30
- }
31
- return {
32
- add(id, entry) {
33
- map.set(id, entry);
34
- dirty = true;
35
- },
36
- remove(id) {
37
- map.delete(id);
38
- dirty = true;
39
- },
40
- resolve() {
41
- rebuild();
42
- return cachedTags;
43
- },
44
- resolveTitleTemplate() {
45
- rebuild();
46
- return cachedTitleTemplate;
47
- },
48
- resolveHtmlAttrs() {
49
- rebuild();
50
- return cachedHtmlAttrs;
51
- },
52
- resolveBodyAttrs() {
53
- rebuild();
54
- return cachedBodyAttrs;
55
- }
56
- };
3
+ //#region src/ssr.d.ts
4
+ /**
5
+ * Render a Pyreon app to an HTML fragment + a serialized <head> string.
6
+ *
7
+ * The returned `head` string can be injected directly into your HTML template:
8
+ *
9
+ * @example
10
+ * const { html, head } = await renderWithHead(h(App, null))
11
+ * const page = `<!DOCTYPE html>
12
+ * <html>
13
+ * <head>
14
+ * <meta charset="UTF-8" />
15
+ * ${head}
16
+ * </head>
17
+ * <body><div id="app">${html}</div></body>
18
+ * </html>`
19
+ */
20
+ interface RenderWithHeadResult {
21
+ html: string;
22
+ head: string;
23
+ /** Attributes to set on the <html> element */
24
+ htmlAttrs: Record<string, string>;
25
+ /** Attributes to set on the <body> element */
26
+ bodyAttrs: Record<string, string>;
57
27
  }
58
- async function renderWithHead(app) {
59
- const ctx = createHeadContext();
60
- function HeadInjector() {
61
- pushContext(new Map([[HeadContext.id, ctx]]));
62
- return app;
63
- }
64
- const html = await renderToString(h(HeadInjector, null));
65
- const titleTemplate = ctx.resolveTitleTemplate();
66
- return {
67
- html,
68
- head: ctx.resolve().map(tag => serializeTag(tag, titleTemplate)).join("\n "),
69
- htmlAttrs: ctx.resolveHtmlAttrs(),
70
- bodyAttrs: ctx.resolveBodyAttrs()
71
- };
72
- }
73
- function serializeTag(tag, titleTemplate) {
74
- if (tag.tag === "title") {
75
- const raw = tag.children || "";
76
- return `<title>${esc(titleTemplate ? typeof titleTemplate === "function" ? titleTemplate(raw) : titleTemplate.replace(/%s/g, raw) : raw)}</title>`;
77
- }
78
- const props = tag.props;
79
- const attrs = props ? Object.entries(props).map(([k, v]) => `${k}="${esc(v)}"`).join(" ") : "";
80
- const open = attrs ? `<${tag.tag} ${attrs}` : `<${tag.tag}`;
81
- if (VOID_TAGS.has(tag.tag)) return `${open} />`;
82
- return `${open}>${(tag.children || "").replace(/<\/(script|style|noscript)/gi, "<\\/$1").replace(/<!--/g, "<\\!--")}</${tag.tag}>`;
83
- }
84
- function esc(s) {
85
- return ESC_RE.test(s) ? s.replace(ESC_RE, ch => ESC_MAP[ch]) : s;
86
- }
87
-
28
+ declare function renderWithHead(app: VNode): Promise<RenderWithHeadResult>;
88
29
  //#endregion
89
- export { renderWithHead };
90
- //# sourceMappingURL=ssr.d.ts.map
30
+ export { RenderWithHeadResult, renderWithHead };
31
+ //# sourceMappingURL=ssr2.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ssr.d.ts","names":[],"sources":["../../../src/context.ts","../../../src/ssr.ts"],"mappings":"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"}
1
+ {"version":3,"file":"ssr2.d.ts","names":[],"sources":["../../../src/ssr.ts"],"mappings":";;;;;AAwBA;;;;;;;;;;;;AASA;;UATiB,oBAAA;EACf,IAAA;EACA,IAAA;EAOgD;EALhD,SAAA,EAAW,MAAA;EAK4C;EAHvD,SAAA,EAAW,MAAA;AAAA;AAAA,iBAGS,cAAA,CAAe,GAAA,EAAK,KAAA,GAAQ,OAAA,CAAQ,oBAAA"}
@@ -1,212 +1,136 @@
1
- import { createContext, onMount, onUnmount, useContext } from "@pyreon/core";
2
- import { effect } from "@pyreon/reactivity";
3
-
4
- //#region src/context.ts
5
-
6
- /**
7
- * Sync the resolved head tags to the real DOM <head>.
8
- * Uses incremental diffing: matches existing elements by key, patches attributes
9
- * in-place, adds new elements, and removes stale ones.
10
- * Also syncs htmlAttrs, bodyAttrs, and applies titleTemplate.
11
- * No-op on the server (typeof document === "undefined").
12
- */
13
- function patchExistingTag(found, tag, kept) {
14
- kept.add(found.getAttribute(ATTR));
15
- patchAttrs(found, tag.props);
16
- const content = String(tag.children);
17
- if (found.textContent !== content) found.textContent = content;
1
+ //#region src/context.d.ts
2
+ /** Standard `<meta>` tag attributes. Catches typos like `{ naem: "description" }`. */
3
+ interface MetaTag {
4
+ /** Standard meta name (e.g. "description", "viewport", "robots") */
5
+ name?: string;
6
+ /** Open Graph / social property (e.g. "og:title", "twitter:card") */
7
+ property?: string;
8
+ /** HTTP equivalent header (e.g. "refresh", "content-type") */
9
+ "http-equiv"?: string;
10
+ /** Value associated with name, property, or http-equiv */
11
+ content?: string;
12
+ /** Document character encoding (e.g. "utf-8") */
13
+ charset?: string;
14
+ /** Schema.org itemprop */
15
+ itemprop?: string;
16
+ /** Media condition for applicability (e.g. "(prefers-color-scheme: dark)") */
17
+ media?: string;
18
18
  }
19
- function createNewTag(tag) {
20
- const el = document.createElement(tag.tag);
21
- const key = tag.key;
22
- el.setAttribute(ATTR, key);
23
- for (const [k, v] of Object.entries(tag.props)) el.setAttribute(k, v);
24
- if (tag.children) el.textContent = tag.children;
25
- document.head.appendChild(el);
26
- managedElements.set(key, el);
19
+ /** Standard `<link>` tag attributes. */
20
+ interface LinkTag {
21
+ /** Relationship to the current document (e.g. "stylesheet", "icon", "canonical") */
22
+ rel?: string;
23
+ /** URL of the linked resource */
24
+ href?: string;
25
+ /** Resource type hint for preloading (e.g. "style", "script", "font") */
26
+ as?: string;
27
+ /** MIME type (e.g. "text/css", "image/png") */
28
+ type?: string;
29
+ /** Media query for conditional loading */
30
+ media?: string;
31
+ /** CORS mode */
32
+ crossorigin?: string;
33
+ /** Subresource integrity hash */
34
+ integrity?: string;
35
+ /** Icon sizes (e.g. "32x32", "any") */
36
+ sizes?: string;
37
+ /** Language of the linked resource */
38
+ hreflang?: string;
39
+ /** Title for the link (used for alternate stylesheets) */
40
+ title?: string;
41
+ /** Fetch priority hint */
42
+ fetchpriority?: "high" | "low" | "auto";
43
+ /** Referrer policy */
44
+ referrerpolicy?: string;
45
+ /** Image source set for preloading responsive images */
46
+ imagesrcset?: string;
47
+ /** Image sizes for preloading responsive images */
48
+ imagesizes?: string;
49
+ /** Disable the resource (for stylesheets) */
50
+ disabled?: string;
51
+ /** Color for mask-icon */
52
+ color?: string;
27
53
  }
28
- function syncDom(ctx) {
29
- if (typeof document === "undefined") return;
30
- const tags = ctx.resolve();
31
- const titleTemplate = ctx.resolveTitleTemplate();
32
- let needsSeed = managedElements.size === 0;
33
- if (!needsSeed) {
34
- const sample = managedElements.values().next().value;
35
- if (sample && !sample.isConnected) {
36
- managedElements.clear();
37
- needsSeed = true;
38
- }
39
- }
40
- if (needsSeed) {
41
- const existing = document.head.querySelectorAll(`[${ATTR}]`);
42
- for (const el of existing) managedElements.set(el.getAttribute(ATTR), el);
43
- }
44
- const kept = /* @__PURE__ */new Set();
45
- for (const tag of tags) {
46
- if (tag.tag === "title") {
47
- document.title = applyTitleTemplate(String(tag.children), titleTemplate);
48
- continue;
49
- }
50
- const key = tag.key;
51
- const found = managedElements.get(key);
52
- if (found && found.tagName.toLowerCase() === tag.tag) patchExistingTag(found, tag, kept);else {
53
- if (found) {
54
- found.remove();
55
- managedElements.delete(key);
56
- }
57
- createNewTag(tag);
58
- kept.add(key);
59
- }
60
- }
61
- for (const [key, el] of managedElements) if (!kept.has(key)) {
62
- el.remove();
63
- managedElements.delete(key);
64
- }
65
- syncElementAttrs(document.documentElement, ctx.resolveHtmlAttrs());
66
- syncElementAttrs(document.body, ctx.resolveBodyAttrs());
54
+ /** Standard `<script>` tag attributes. */
55
+ interface ScriptTag {
56
+ /** External script URL */
57
+ src?: string;
58
+ /** Script MIME type or module type (e.g. "module", "importmap") */
59
+ type?: string;
60
+ /** Load asynchronously */
61
+ async?: string;
62
+ /** Defer execution until document is parsed */
63
+ defer?: string;
64
+ /** CORS mode */
65
+ crossorigin?: string;
66
+ /** Subresource integrity hash */
67
+ integrity?: string;
68
+ /** Exclude from module-supporting browsers */
69
+ nomodule?: string;
70
+ /** Referrer policy */
71
+ referrerpolicy?: string;
72
+ /** Fetch priority hint */
73
+ fetchpriority?: string;
74
+ /** Inline script content */
75
+ children?: string;
67
76
  }
68
- /** Patch an element's attributes to match the desired props. */
69
- function patchAttrs(el, props) {
70
- for (let i = el.attributes.length - 1; i >= 0; i--) {
71
- const attr = el.attributes[i];
72
- if (!attr || attr.name === ATTR) continue;
73
- if (!(attr.name in props)) el.removeAttribute(attr.name);
74
- }
75
- for (const [k, v] of Object.entries(props)) if (el.getAttribute(k) !== v) el.setAttribute(k, v);
77
+ /** Standard `<style>` tag attributes. */
78
+ interface StyleTag {
79
+ /** Inline CSS content (required) */
80
+ children: string;
81
+ /** Media query for conditional styles */
82
+ media?: string;
83
+ /** Nonce for CSP */
84
+ nonce?: string;
85
+ /** Title for alternate stylesheets */
86
+ title?: string;
87
+ /** Render-blocking behavior */
88
+ blocking?: string;
76
89
  }
77
- function applyTitleTemplate(title, template) {
78
- if (!template) return title;
79
- if (typeof template === "function") return template(title);
80
- return template.replace(/%s/g, title);
90
+ /** Standard `<base>` tag attributes. */
91
+ interface BaseTag {
92
+ /** Base URL for relative URLs in the document */
93
+ href?: string;
94
+ /** Default target for links and forms */
95
+ target?: "_blank" | "_self" | "_parent" | "_top";
81
96
  }
82
- /** Sync pyreon-managed attributes on <html> or <body>. */
83
- function syncElementAttrs(el, attrs) {
84
- const managed = el.getAttribute(`${ATTR}-attrs`);
85
- if (managed) {
86
- for (const name of managed.split(",")) if (name && !(name in attrs)) el.removeAttribute(name);
87
- }
88
- const keys = [];
89
- for (const [k, v] of Object.entries(attrs)) {
90
- keys.push(k);
91
- if (el.getAttribute(k) !== v) el.setAttribute(k, v);
92
- }
93
- if (keys.length > 0) el.setAttribute(`${ATTR}-attrs`, keys.join(","));else if (managed) el.removeAttribute(`${ATTR}-attrs`);
97
+ interface UseHeadInput {
98
+ title?: string;
99
+ /**
100
+ * Title template — use `%s` as a placeholder for the page title.
101
+ * Applied to the resolved title after deduplication.
102
+ * @example useHead({ titleTemplate: "%s | My App" })
103
+ */
104
+ titleTemplate?: string | ((title: string) => string);
105
+ meta?: MetaTag[];
106
+ link?: LinkTag[];
107
+ script?: ScriptTag[];
108
+ style?: StyleTag[];
109
+ noscript?: {
110
+ children: string;
111
+ }[];
112
+ /** Convenience: emits a <script type="application/ld+json"> tag with JSON.stringify'd content */
113
+ jsonLd?: Record<string, unknown> | Record<string, unknown>[];
114
+ base?: BaseTag;
115
+ /** Attributes to set on the <html> element (e.g. { lang: "en", dir: "ltr" }) */
116
+ htmlAttrs?: Record<string, string>;
117
+ /** Attributes to set on the <body> element (e.g. { class: "dark" }) */
118
+ bodyAttrs?: Record<string, string>;
94
119
  }
95
-
96
120
  //#endregion
97
- //#region src/use-head.ts
98
- /** Cast a strict tag interface to the internal props format, stripping undefined values */
99
- function toProps(obj) {
100
- const result = {};
101
- for (const [k, v] of Object.entries(obj)) if (v !== void 0) result[k] = v;
102
- return result;
103
- }
104
- function buildEntry(o) {
105
- const tags = [];
106
- if (o.title != null) tags.push({
107
- tag: "title",
108
- key: "title",
109
- children: o.title
110
- });
111
- o.meta?.forEach((m, i) => {
112
- tags.push({
113
- tag: "meta",
114
- key: m.name ?? m.property ?? `meta-${i}`,
115
- props: toProps(m)
116
- });
117
- });
118
- o.link?.forEach((l, i) => {
119
- tags.push({
120
- tag: "link",
121
- key: l.href ? `link-${l.rel || ""}-${l.href}` : l.rel ? `link-${l.rel}` : `link-${i}`,
122
- props: toProps(l)
123
- });
124
- });
125
- o.script?.forEach((s, i) => {
126
- const {
127
- children,
128
- ...rest
129
- } = s;
130
- tags.push({
131
- tag: "script",
132
- key: s.src ?? `script-${i}`,
133
- props: toProps(rest),
134
- ...(children != null ? {
135
- children
136
- } : {})
137
- });
138
- });
139
- o.style?.forEach((s, i) => {
140
- const {
141
- children,
142
- ...rest
143
- } = s;
144
- tags.push({
145
- tag: "style",
146
- key: `style-${i}`,
147
- props: toProps(rest),
148
- children
149
- });
150
- });
151
- o.noscript?.forEach((ns, i) => {
152
- tags.push({
153
- tag: "noscript",
154
- key: `noscript-${i}`,
155
- children: ns.children
156
- });
157
- });
158
- if (o.jsonLd) tags.push({
159
- tag: "script",
160
- key: "jsonld",
161
- props: {
162
- type: "application/ld+json"
163
- },
164
- children: JSON.stringify(o.jsonLd)
165
- });
166
- if (o.base) tags.push({
167
- tag: "base",
168
- key: "base",
169
- props: toProps(o.base)
170
- });
171
- return {
172
- tags,
173
- titleTemplate: o.titleTemplate,
174
- htmlAttrs: o.htmlAttrs,
175
- bodyAttrs: o.bodyAttrs
176
- };
177
- }
121
+ //#region src/use-head.d.ts
178
122
  /**
179
- * Register head tags (title, meta, link, script, style, noscript, base, jsonLd)
180
- * for the current component.
181
- *
182
- * Accepts a static object or a reactive getter:
183
- * useHead({ title: "My Page", meta: [{ name: "description", content: "..." }] })
184
- * useHead(() => ({ title: `${count()} items` })) // updates when signal changes
185
- *
186
- * Tags are deduplicated by key — innermost component wins.
187
- * Requires a <HeadProvider> (CSR) or renderWithHead() (SSR) ancestor.
188
- */
189
- function useHead(input) {
190
- const ctx = useContext(HeadContext);
191
- if (!ctx) return;
192
- const id = Symbol();
193
- if (typeof input === "function") {
194
- if (typeof document !== "undefined") effect(() => {
195
- ctx.add(id, buildEntry(input()));
196
- syncDom(ctx);
197
- });else ctx.add(id, buildEntry(input()));
198
- } else {
199
- ctx.add(id, buildEntry(input));
200
- onMount(() => {
201
- syncDom(ctx);
202
- });
203
- }
204
- onUnmount(() => {
205
- ctx.remove(id);
206
- syncDom(ctx);
207
- });
208
- }
209
-
123
+ * Register head tags (title, meta, link, script, style, noscript, base, jsonLd)
124
+ * for the current component.
125
+ *
126
+ * Accepts a static object or a reactive getter:
127
+ * useHead({ title: "My Page", meta: [{ name: "description", content: "..." }] })
128
+ * useHead(() => ({ title: `${count()} items` })) // updates when signal changes
129
+ *
130
+ * Tags are deduplicated by key — innermost component wins.
131
+ * Requires a <HeadProvider> (CSR) or renderWithHead() (SSR) ancestor.
132
+ */
133
+ declare function useHead(input: UseHeadInput | (() => UseHeadInput)): void;
210
134
  //#endregion
211
135
  export { useHead };
212
- //# sourceMappingURL=use-head.d.ts.map
136
+ //# sourceMappingURL=use-head2.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-head.d.ts","names":[],"sources":["../../../src/context.ts","../../../src/dom.ts","../../../src/use-head.ts"],"mappings":";;;;;;;;;;;;ACcA,SAAS,gBAAA,CACP,KAAA,EACA,GAAA,EACA,IAAA,EACM;EACN,IAAA,CAAK,GAAA,CAAI,KAAA,CAAM,YAAA,CAAa,IAAA,CAAK,CAAW;EAC5C,UAAA,CAAW,KAAA,EAAO,GAAA,CAAI,KAAA,CAAgC;EACtD,MAAM,OAAA,GAAU,MAAA,CAAO,GAAA,CAAI,QAAA,CAAS;EACpC,IAAI,KAAA,CAAM,WAAA,KAAgB,OAAA,EAAS,KAAA,CAAM,WAAA,GAAc,OAAA;;AAGzD,SAAS,YAAA,CAAa,GAAA,EAKb;EACP,MAAM,EAAA,GAAK,QAAA,CAAS,aAAA,CAAc,GAAA,CAAI,GAAA,CAAI;EAC1C,MAAM,GAAA,GAAM,GAAA,CAAI,GAAA;EAChB,EAAA,CAAG,YAAA,CAAa,IAAA,EAAM,GAAA,CAAI;EAC1B,KAAK,MAAM,CAAC,CAAA,EAAG,CAAA,CAAA,IAAM,MAAA,CAAO,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAgC,EACtE,EAAA,CAAG,YAAA,CAAa,CAAA,EAAG,CAAA,CAAE;EAEvB,IAAI,GAAA,CAAI,QAAA,EAAU,EAAA,CAAG,WAAA,GAAc,GAAA,CAAI,QAAA;EACvC,QAAA,CAAS,IAAA,CAAK,WAAA,CAAY,EAAA,CAAG;EAC7B,eAAA,CAAgB,GAAA,CAAI,GAAA,EAAK,EAAA,CAAG;;AAG9B,SAAgB,OAAA,CAAQ,GAAA,EAA6B;EACnD,IAAI,OAAO,QAAA,KAAa,WAAA,EAAa;EAErC,MAAM,IAAA,GAAO,GAAA,CAAI,OAAA,CAAA,CAAS;EAC1B,MAAM,aAAA,GAAgB,GAAA,CAAI,oBAAA,CAAA,CAAsB;EAGhD,IAAI,SAAA,GAAY,eAAA,CAAgB,IAAA,KAAS,CAAA;EACzC,IAAI,CAAC,SAAA,EAAW;IAEd,MAAM,MAAA,GAAS,eAAA,CAAgB,MAAA,CAAA,CAAQ,CAAC,IAAA,CAAA,CAAM,CAAC,KAAA;IAC/C,IAAI,MAAA,IAAU,CAAC,MAAA,CAAO,WAAA,EAAa;MACjC,eAAA,CAAgB,KAAA,CAAA,CAAO;MACvB,SAAA,GAAY,IAAA;;;EAGhB,IAAI,SAAA,EAAW;IACb,MAAM,QAAA,GAAW,QAAA,CAAS,IAAA,CAAK,gBAAA,CAAiB,IAAI,IAAA,GAAK,CAAG;IAC5D,KAAK,MAAM,EAAA,IAAM,QAAA,EACf,eAAA,CAAgB,GAAA,CAAI,EAAA,CAAG,YAAA,CAAa,IAAA,CAAK,EAAY,EAAA,CAAG;;EAI5D,MAAM,IAAA,GAAA,eAAO,IAAI,GAAA,CAAA,CAAa;EAE9B,KAAK,MAAM,GAAA,IAAO,IAAA,EAAM;IACtB,IAAI,GAAA,CAAI,GAAA,KAAQ,OAAA,EAAS;MACvB,QAAA,CAAS,KAAA,GAAQ,kBAAA,CAAmB,MAAA,CAAO,GAAA,CAAI,QAAA,CAAS,EAAE,aAAA,CAAc;MACxE;;IAGF,MAAM,GAAA,GAAM,GAAA,CAAI,GAAA;IAChB,MAAM,KAAA,GAAQ,eAAA,CAAgB,GAAA,CAAI,GAAA,CAAI;IAEtC,IAAI,KAAA,IAAS,KAAA,CAAM,OAAA,CAAQ,WAAA,CAAA,CAAa,KAAK,GAAA,CAAI,GAAA,EAC/C,gBAAA,CAAiB,KAAA,EAAO,GAAA,EAA6D,IAAA,CAAK,CAAA,KACrF;MACL,IAAI,KAAA,EAAO;QACT,KAAA,CAAM,MAAA,CAAA,CAAQ;QACd,eAAA,CAAgB,MAAA,CAAO,GAAA,CAAI;;MAE7B,YAAA,CACE,GAAA,CACD;MACD,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI;;;EAKjB,KAAK,MAAM,CAAC,GAAA,EAAK,EAAA,CAAA,IAAO,eAAA,EACtB,IAAI,CAAC,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,EAAE;IAClB,EAAA,CAAG,MAAA,CAAA,CAAQ;IACX,eAAA,CAAgB,MAAA,CAAO,GAAA,CAAI;;EAI/B,gBAAA,CAAiB,QAAA,CAAS,eAAA,EAAiB,GAAA,CAAI,gBAAA,CAAA,CAAkB,CAAC;EAClE,gBAAA,CAAiB,QAAA,CAAS,IAAA,EAAM,GAAA,CAAI,gBAAA,CAAA,CAAkB,CAAC;;;AAIzD,SAAS,UAAA,CAAW,EAAA,EAAa,KAAA,EAAqC;EACpE,KAAK,IAAI,CAAA,GAAI,EAAA,CAAG,UAAA,CAAW,MAAA,GAAS,CAAA,EAAG,CAAA,IAAK,CAAA,EAAG,CAAA,EAAA,EAAK;IAClD,MAAM,IAAA,GAAO,EAAA,CAAG,UAAA,CAAW,CAAA,CAAA;IAC3B,IAAI,CAAC,IAAA,IAAQ,IAAA,CAAK,IAAA,KAAS,IAAA,EAAM;IACjC,IAAI,EAAE,IAAA,CAAK,IAAA,IAAQ,KAAA,CAAA,EAAQ,EAAA,CAAG,eAAA,CAAgB,IAAA,CAAK,IAAA,CAAK;;EAE1D,KAAK,MAAM,CAAC,CAAA,EAAG,CAAA,CAAA,IAAM,MAAA,CAAO,OAAA,CAAQ,KAAA,CAAM,EACxC,IAAI,EAAA,CAAG,YAAA,CAAa,CAAA,CAAE,KAAK,CAAA,EAAG,EAAA,CAAG,YAAA,CAAa,CAAA,EAAG,CAAA,CAAE;;AAIvD,SAAS,kBAAA,CACP,KAAA,EACA,QAAA,EACQ;EACR,IAAI,CAAC,QAAA,EAAU,OAAO,KAAA;EACtB,IAAI,OAAO,QAAA,KAAa,UAAA,EAAY,OAAO,QAAA,CAAS,KAAA,CAAM;EAC1D,OAAO,QAAA,CAAS,OAAA,CAAQ,KAAA,EAAO,KAAA,CAAM;;;AAIvC,SAAS,gBAAA,CAAiB,EAAA,EAAa,KAAA,EAAqC;EAE1E,MAAM,OAAA,GAAU,EAAA,CAAG,YAAA,CAAa,GAAG,IAAA,QAAK,CAAQ;EAChD,IAAI,OAAA,EACF;SAAK,MAAM,IAAA,IAAQ,OAAA,CAAQ,KAAA,CAAM,GAAA,CAAI,EACnC,IAAI,IAAA,IAAQ,EAAE,IAAA,IAAQ,KAAA,CAAA,EAAQ,EAAA,CAAG,eAAA,CAAgB,IAAA,CAAK;;EAG1D,MAAM,IAAA,GAAiB,EAAE;EACzB,KAAK,MAAM,CAAC,CAAA,EAAG,CAAA,CAAA,IAAM,MAAA,CAAO,OAAA,CAAQ,KAAA,CAAM,EAAE;IAC1C,IAAA,CAAK,IAAA,CAAK,CAAA,CAAE;IACZ,IAAI,EAAA,CAAG,YAAA,CAAa,CAAA,CAAE,KAAK,CAAA,EAAG,EAAA,CAAG,YAAA,CAAa,CAAA,EAAG,CAAA,CAAE;;EAErD,IAAI,IAAA,CAAK,MAAA,GAAS,CAAA,EAChB,EAAA,CAAG,YAAA,CAAa,GAAG,IAAA,QAAK,EAAS,IAAA,CAAK,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA,SACvC,OAAA,EACT,EAAA,CAAG,eAAA,CAAgB,GAAG,IAAA,QAAK,CAAQ;;;;;;ACrIvC,SAAS,OAAA,CAAQ,GAAA,EAAiE;EAChF,MAAM,MAAA,GAAiC,CAAA,CAAE;EACzC,KAAK,MAAM,CAAC,CAAA,EAAG,CAAA,CAAA,IAAM,MAAA,CAAO,OAAA,CAAQ,GAAA,CAAI,EACtC,IAAI,CAAA,KAAM,KAAA,CAAA,EAAW,MAAA,CAAO,CAAA,CAAA,GAAK,CAAA;EAEnC,OAAO,MAAA;;AAGT,SAAS,UAAA,CAAW,CAAA,EAA4B;EAC9C,MAAM,IAAA,GAAkB,EAAE;EAC1B,IAAI,CAAA,CAAE,KAAA,IAAS,IAAA,EAAM,IAAA,CAAK,IAAA,CAAK;IAAE,GAAA,EAAK,OAAA;IAAS,GAAA,EAAK,OAAA;IAAS,QAAA,EAAU,CAAA,CAAE;GAAO,CAAC;EACjF,CAAA,CAAE,IAAA,EAAM,OAAA,CAAA,CAAS,CAAA,EAAG,CAAA,KAAM;IACxB,IAAA,CAAK,IAAA,CAAK;MACR,GAAA,EAAK,MAAA;MACL,GAAA,EAAK,CAAA,CAAE,IAAA,IAAQ,CAAA,CAAE,QAAA,IAAY,QAAQ,CAAA,EAAA;MACrC,KAAA,EAAO,OAAA,CAAQ,CAAA;KAChB,CAAC;IACF;EACF,CAAA,CAAE,IAAA,EAAM,OAAA,CAAA,CAAS,CAAA,EAAG,CAAA,KAAM;IACxB,IAAA,CAAK,IAAA,CAAK;MACR,GAAA,EAAK,MAAA;MACL,GAAA,EAAK,CAAA,CAAE,IAAA,GAAO,QAAQ,CAAA,CAAE,GAAA,IAAO,EAAA,IAAM,CAAA,CAAE,IAAA,EAAA,GAAS,CAAA,CAAE,GAAA,GAAM,QAAQ,CAAA,CAAE,GAAA,EAAA,GAAQ,QAAQ,CAAA,EAAA;MAClF,KAAA,EAAO,OAAA,CAAQ,CAAA;KAChB,CAAC;IACF;EACF,CAAA,CAAE,MAAA,EAAQ,OAAA,CAAA,CAAS,CAAA,EAAG,CAAA,KAAM;IAC1B,MAAM;MAAE,QAAA;MAAU,GAAG;IAAA,CAAA,GAAS,CAAA;IAC9B,IAAA,CAAK,IAAA,CAAK;MACR,GAAA,EAAK,QAAA;MACL,GAAA,EAAK,CAAA,CAAE,GAAA,IAAO,UAAU,CAAA,EAAA;MACxB,KAAA,EAAO,OAAA,CAAQ,IAAA,CAA2C;MAC1D,IAAI,QAAA,IAAY,IAAA,GAAO;QAAE;MAAA,CAAU,GAAG,CAAA,CAAE;KACzC,CAAC;IACF;EACF,CAAA,CAAE,KAAA,EAAO,OAAA,CAAA,CAAS,CAAA,EAAG,CAAA,KAAM;IACzB,MAAM;MAAE,QAAA;MAAU,GAAG;IAAA,CAAA,GAAS,CAAA;IAC9B,IAAA,CAAK,IAAA,CAAK;MACR,GAAA,EAAK,OAAA;MACL,GAAA,EAAK,SAAS,CAAA,EAAA;MACd,KAAA,EAAO,OAAA,CAAQ,IAAA,CAA2C;MAC1D;KACD,CAAC;IACF;EACF,CAAA,CAAE,QAAA,EAAU,OAAA,CAAA,CAAS,EAAA,EAAI,CAAA,KAAM;IAC7B,IAAA,CAAK,IAAA,CAAK;MAAE,GAAA,EAAK,UAAA;MAAY,GAAA,EAAK,YAAY,CAAA,EAAA;MAAK,QAAA,EAAU,EAAA,CAAG;KAAU,CAAC;IAC3E;EACF,IAAI,CAAA,CAAE,MAAA,EACJ,IAAA,CAAK,IAAA,CAAK;IACR,GAAA,EAAK,QAAA;IACL,GAAA,EAAK,QAAA;IACL,KAAA,EAAO;MAAE,IAAA,EAAM;IAAA,CAAuB;IACtC,QAAA,EAAU,IAAA,CAAK,SAAA,CAAU,CAAA,CAAE,MAAA;GAC5B,CAAC;EAEJ,IAAI,CAAA,CAAE,IAAA,EACJ,IAAA,CAAK,IAAA,CAAK;IACR,GAAA,EAAK,MAAA;IACL,GAAA,EAAK,MAAA;IACL,KAAA,EAAO,OAAA,CAAQ,CAAA,CAAE,IAAA;GAClB,CAAC;EACJ,OAAO;IACL,IAAA;IACA,aAAA,EAAe,CAAA,CAAE,aAAA;IACjB,SAAA,EAAW,CAAA,CAAE,SAAA;IACb,SAAA,EAAW,CAAA,CAAE;GACd;;;;;;;;;;;;;AAcH,SAAgB,OAAA,CAAQ,KAAA,EAAkD;EACxE,MAAM,GAAA,GAAM,UAAA,CAAW,WAAA,CAAY;EACnC,IAAI,CAAC,GAAA,EAAK;EAEV,MAAM,EAAA,GAAK,MAAA,CAAA,CAAQ;EAEnB,IAAI,OAAO,KAAA,KAAU,UAAA;IACnB,IAAI,OAAO,QAAA,KAAa,WAAA,EAEtB,MAAA,CAAA,MAAa;MACX,GAAA,CAAI,GAAA,CAAI,EAAA,EAAI,UAAA,CAAW,KAAA,CAAA,CAAO,CAAC,CAAC;MAChC,OAAA,CAAQ,GAAA,CAAI;MACZ,CAAA,KAGF,GAAA,CAAI,GAAA,CAAI,EAAA,EAAI,UAAA,CAAW,KAAA,CAAA,CAAO,CAAC,CAAC;EAAA,OAE7B;IACL,GAAA,CAAI,GAAA,CAAI,EAAA,EAAI,UAAA,CAAW,KAAA,CAAM,CAAC;IAC9B,OAAA,CAAA,MAAc;MACZ,OAAA,CAAQ,GAAA,CAAI;MACZ;;EAGJ,SAAA,CAAA,MAAgB;IACd,GAAA,CAAI,MAAA,CAAO,EAAA,CAAG;IACd,OAAA,CAAQ,GAAA,CAAI;IACZ"}
1
+ {"version":3,"file":"use-head2.d.ts","names":[],"sources":["../../../src/context.ts","../../../src/use-head.ts"],"mappings":";;UAsBiB,OAAA;EAkBA;EAhBf,IAAA;;EAEA,QAAA;EAgBA;EAdA,YAAA;EAkBA;EAhBA,OAAA;EAoBA;EAlBA,OAAA;EAsBA;EApBA,QAAA;EAwBA;EAtBA,KAAA;AAAA;;UAIe,OAAA;EA4Bf;EA1BA,GAAA;EA8BA;EA5BA,IAAA;EA4BK;EA1BL,EAAA;EA8BwB;EA5BxB,IAAA;EA4BwB;EA1BxB,KAAA;EA8BA;EA5BA,WAAA;EAgCA;EA9BA,SAAA;EAkCA;EAhCA,KAAA;EAoCA;EAlCA,QAAA;EAsCA;EApCA,KAAA;EAoCQ;EAlCR,aAAA;EAsCuB;EApCvB,cAAA;EAoCuB;EAlCvB,WAAA;EAsCA;EApCA,UAAA;EAwCA;EAtCA,QAAA;EAwCQ;EAtCR,KAAA;AAAA;;UAIe,SAAA;EAwCf;EAtCA,GAAA;EA2Ce;EAzCf,IAAA;;EAEA,KAAA;EAgDO;EA9CP,KAAA;EAgDQ;EA9CR,WAAA;EAiDmC;EA/CnC,SAAA;EAkDY;EAhDZ,QAAA;EAkDkB;EAhDlB,cAAA;EA8BA;EA5BA,aAAA;EAkC2B;EAhC3B,QAAA;AAAA;;UAIe,QAAA;EA+Bf;EA7BA,QAAA;EA8BA;EA5BA,KAAA;EA6BA;EA3BA,KAAA;EA6BA;EA3BA,KAAA;EA2BmC;EAzBnC,QAAA;AAAA;;UAIe,OAAA;EA0Bf;EAxBA,IAAA;EAwBkB;EAtBlB,MAAA;AAAA;AAAA,UAGe,YAAA;EACf,KAAA;ECpCc;;;;;ED0Cd,aAAA,cAA2B,KAAA;EAC3B,IAAA,GAAO,OAAA;EACP,IAAA,GAAO,OAAA;EACP,MAAA,GAAS,SAAA;EACT,KAAA,GAAQ,QAAA;EACR,QAAA;IAAa,QAAA;EAAA;;EAEb,MAAA,GAAS,MAAA,oBAA0B,MAAA;EACnC,IAAA,GAAO,OAAA;;EAEP,SAAA,GAAY,MAAA;;EAEZ,SAAA,GAAY,MAAA;AAAA;;;;;AAtHd;;;;;;;;;iBCgEgB,OAAA,CAAQ,KAAA,EAAO,YAAA,UAAsB,YAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/head",
3
- "version": "0.5.6",
3
+ "version": "0.6.0",
4
4
  "description": "Head tag management for Pyreon — works in SSR and CSR",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -54,9 +54,9 @@
54
54
  "prepublishOnly": "bun run build"
55
55
  },
56
56
  "dependencies": {
57
- "@pyreon/core": "^0.5.6",
58
- "@pyreon/reactivity": "^0.5.6",
59
- "@pyreon/runtime-server": "^0.5.6"
57
+ "@pyreon/core": "^0.6.0",
58
+ "@pyreon/reactivity": "^0.6.0",
59
+ "@pyreon/runtime-server": "^0.6.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@happy-dom/global-registrator": "*",
package/src/provider.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { ComponentFn, Props, VNodeChild } from "@pyreon/core"
2
- import { onUnmount, popContext, pushContext } from "@pyreon/core"
2
+ import { provide } from "@pyreon/core"
3
3
  import type { HeadContextValue } from "./context"
4
4
  import { createHeadContext, HeadContext } from "./context"
5
5
 
@@ -24,12 +24,8 @@ export interface HeadProviderProps extends Props {
24
24
  */
25
25
  export const HeadProvider: ComponentFn<HeadProviderProps> = (props) => {
26
26
  const ctx = props.context ?? createHeadContext()
27
- // Push context frame synchronously (before children mount) so all descendants
28
- // can read HeadContext via useContext(). Pop on unmount for correct cleanup.
29
- const frame = new Map([[HeadContext.id, ctx]])
30
- pushContext(frame)
31
- onUnmount(() => popContext())
27
+ provide(HeadContext, ctx)
32
28
 
33
29
  const ch = props.children
34
- return (typeof ch === "function" ? (ch as () => VNodeChild)() : ch) as ReturnType<ComponentFn>
30
+ return typeof ch === "function" ? (ch as () => VNodeChild)() : ch
35
31
  }