@pyreon/head 0.5.3 → 0.5.5
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/lib/analysis/index.js.html +1 -1
- package/lib/analysis/use-head.js.html +1 -1
- package/lib/index.js +11 -5
- package/lib/index.js.map +1 -1
- package/lib/provider.js.map +1 -1
- package/lib/ssr.js.map +1 -1
- package/lib/types/index.d.ts +11 -5
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/provider.d.ts.map +1 -1
- package/lib/types/ssr.d.ts.map +1 -1
- package/lib/types/use-head.d.ts +11 -5
- package/lib/types/use-head.d.ts.map +1 -1
- package/lib/use-head.js +11 -5
- package/lib/use-head.js.map +1 -1
- package/package.json +4 -4
- package/src/context.ts +107 -5
- package/src/index.ts +11 -1
- package/src/use-head.ts +19 -5
|
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
|
|
|
5386
5386
|
</script>
|
|
5387
5387
|
<script>
|
|
5388
5388
|
/*<!--*/
|
|
5389
|
-
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"
|
|
5389
|
+
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"859444d3-1","name":"context.ts"},{"uid":"859444d3-3","name":"provider.ts"},{"uid":"859444d3-5","name":"ssr.ts"},{"uid":"859444d3-7","name":"dom.ts"},{"uid":"859444d3-9","name":"use-head.ts"},{"uid":"859444d3-11","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"859444d3-1":{"renderedLength":1373,"gzipLength":509,"brotliLength":0,"metaUid":"859444d3-0"},"859444d3-3":{"renderedLength":723,"gzipLength":429,"brotliLength":0,"metaUid":"859444d3-2"},"859444d3-5":{"renderedLength":1370,"gzipLength":701,"brotliLength":0,"metaUid":"859444d3-4"},"859444d3-7":{"renderedLength":3401,"gzipLength":1293,"brotliLength":0,"metaUid":"859444d3-6"},"859444d3-9":{"renderedLength":2462,"gzipLength":1063,"brotliLength":0,"metaUid":"859444d3-8"},"859444d3-11":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"859444d3-10"}},"nodeMetas":{"859444d3-0":{"id":"/src/context.ts","moduleParts":{"index.js":"859444d3-1"},"imported":[{"uid":"859444d3-12"}],"importedBy":[{"uid":"859444d3-10"},{"uid":"859444d3-2"},{"uid":"859444d3-4"},{"uid":"859444d3-8"}]},"859444d3-2":{"id":"/src/provider.ts","moduleParts":{"index.js":"859444d3-3"},"imported":[{"uid":"859444d3-12"},{"uid":"859444d3-0"}],"importedBy":[{"uid":"859444d3-10"}]},"859444d3-4":{"id":"/src/ssr.ts","moduleParts":{"index.js":"859444d3-5"},"imported":[{"uid":"859444d3-12"},{"uid":"859444d3-13"},{"uid":"859444d3-0"}],"importedBy":[{"uid":"859444d3-10"}]},"859444d3-6":{"id":"/src/dom.ts","moduleParts":{"index.js":"859444d3-7"},"imported":[],"importedBy":[{"uid":"859444d3-8"}]},"859444d3-8":{"id":"/src/use-head.ts","moduleParts":{"index.js":"859444d3-9"},"imported":[{"uid":"859444d3-12"},{"uid":"859444d3-14"},{"uid":"859444d3-0"},{"uid":"859444d3-6"}],"importedBy":[{"uid":"859444d3-10"}]},"859444d3-10":{"id":"/src/index.ts","moduleParts":{"index.js":"859444d3-11"},"imported":[{"uid":"859444d3-0"},{"uid":"859444d3-2"},{"uid":"859444d3-4"},{"uid":"859444d3-8"}],"importedBy":[],"isEntry":true},"859444d3-12":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"859444d3-0"},{"uid":"859444d3-2"},{"uid":"859444d3-4"},{"uid":"859444d3-8"}]},"859444d3-13":{"id":"@pyreon/runtime-server","moduleParts":{},"imported":[],"importedBy":[{"uid":"859444d3-4"}]},"859444d3-14":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"859444d3-8"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
|
|
5390
5390
|
|
|
5391
5391
|
const run = () => {
|
|
5392
5392
|
const width = window.innerWidth;
|
|
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
|
|
|
5386
5386
|
</script>
|
|
5387
5387
|
<script>
|
|
5388
5388
|
/*<!--*/
|
|
5389
|
-
const data = {"version":2,"tree":{"name":"root","children":[{"name":"use-head.js","children":[{"name":"src","children":[{"uid":"
|
|
5389
|
+
const data = {"version":2,"tree":{"name":"root","children":[{"name":"use-head.js","children":[{"name":"src","children":[{"uid":"2d2aa494-1","name":"context.ts"},{"uid":"2d2aa494-3","name":"dom.ts"},{"uid":"2d2aa494-5","name":"use-head.ts"}]}]}],"isRoot":true},"nodeParts":{"2d2aa494-1":{"renderedLength":79,"gzipLength":85,"brotliLength":0,"metaUid":"2d2aa494-0"},"2d2aa494-3":{"renderedLength":3401,"gzipLength":1293,"brotliLength":0,"metaUid":"2d2aa494-2"},"2d2aa494-5":{"renderedLength":2462,"gzipLength":1063,"brotliLength":0,"metaUid":"2d2aa494-4"}},"nodeMetas":{"2d2aa494-0":{"id":"/src/context.ts","moduleParts":{"use-head.js":"2d2aa494-1"},"imported":[{"uid":"2d2aa494-6"}],"importedBy":[{"uid":"2d2aa494-4"}]},"2d2aa494-2":{"id":"/src/dom.ts","moduleParts":{"use-head.js":"2d2aa494-3"},"imported":[],"importedBy":[{"uid":"2d2aa494-4"}]},"2d2aa494-4":{"id":"/src/use-head.ts","moduleParts":{"use-head.js":"2d2aa494-5"},"imported":[{"uid":"2d2aa494-6"},{"uid":"2d2aa494-7"},{"uid":"2d2aa494-0"},{"uid":"2d2aa494-2"}],"importedBy":[],"isEntry":true},"2d2aa494-6":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"2d2aa494-4"},{"uid":"2d2aa494-0"}]},"2d2aa494-7":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"2d2aa494-4"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
|
|
5390
5390
|
|
|
5391
5391
|
const run = () => {
|
|
5392
5392
|
const width = window.innerWidth;
|
package/lib/index.js
CHANGED
|
@@ -226,6 +226,12 @@ function syncElementAttrs(el, attrs) {
|
|
|
226
226
|
|
|
227
227
|
//#endregion
|
|
228
228
|
//#region src/use-head.ts
|
|
229
|
+
/** Cast a strict tag interface to the internal props format, stripping undefined values */
|
|
230
|
+
function toProps(obj) {
|
|
231
|
+
const result = {};
|
|
232
|
+
for (const [k, v] of Object.entries(obj)) if (v !== void 0) result[k] = v;
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
229
235
|
function buildEntry(o) {
|
|
230
236
|
const tags = [];
|
|
231
237
|
if (o.title != null) tags.push({
|
|
@@ -237,14 +243,14 @@ function buildEntry(o) {
|
|
|
237
243
|
tags.push({
|
|
238
244
|
tag: "meta",
|
|
239
245
|
key: m.name ?? m.property ?? `meta-${i}`,
|
|
240
|
-
props: m
|
|
246
|
+
props: toProps(m)
|
|
241
247
|
});
|
|
242
248
|
});
|
|
243
249
|
o.link?.forEach((l, i) => {
|
|
244
250
|
tags.push({
|
|
245
251
|
tag: "link",
|
|
246
252
|
key: l.href ? `link-${l.rel || ""}-${l.href}` : l.rel ? `link-${l.rel}` : `link-${i}`,
|
|
247
|
-
props: l
|
|
253
|
+
props: toProps(l)
|
|
248
254
|
});
|
|
249
255
|
});
|
|
250
256
|
o.script?.forEach((s, i) => {
|
|
@@ -252,7 +258,7 @@ function buildEntry(o) {
|
|
|
252
258
|
tags.push({
|
|
253
259
|
tag: "script",
|
|
254
260
|
key: s.src ?? `script-${i}`,
|
|
255
|
-
props: rest,
|
|
261
|
+
props: toProps(rest),
|
|
256
262
|
...children != null ? { children } : {}
|
|
257
263
|
});
|
|
258
264
|
});
|
|
@@ -261,7 +267,7 @@ function buildEntry(o) {
|
|
|
261
267
|
tags.push({
|
|
262
268
|
tag: "style",
|
|
263
269
|
key: `style-${i}`,
|
|
264
|
-
props: rest,
|
|
270
|
+
props: toProps(rest),
|
|
265
271
|
children
|
|
266
272
|
});
|
|
267
273
|
});
|
|
@@ -281,7 +287,7 @@ function buildEntry(o) {
|
|
|
281
287
|
if (o.base) tags.push({
|
|
282
288
|
tag: "base",
|
|
283
289
|
key: "base",
|
|
284
|
-
props: o.base
|
|
290
|
+
props: toProps(o.base)
|
|
285
291
|
});
|
|
286
292
|
return {
|
|
287
293
|
tags,
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/context.ts","../src/provider.ts","../src/ssr.ts","../src/dom.ts","../src/use-head.ts"],"sourcesContent":["import { createContext } from \"@pyreon/core\"\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface HeadTag {\n /** HTML tag name */\n tag: \"title\" | \"meta\" | \"link\" | \"script\" | \"style\" | \"base\" | \"noscript\"\n /**\n * Deduplication key. Tags with the same key replace each other;\n * innermost component (last added) wins.\n * Example: all components setting the page title use key \"title\".\n */\n key?: string\n /** HTML attributes for the tag */\n props?: Record<string, string>\n /** Text content — for <title>, <script>, <style>, <noscript> */\n children?: string\n}\n\nexport interface UseHeadInput {\n title?: string\n /**\n * Title template — use `%s` as a placeholder for the page title.\n * Applied to the resolved title after deduplication.\n * @example useHead({ titleTemplate: \"%s | My App\" })\n */\n titleTemplate?: string | ((title: string) => string)\n meta?: Record<string, string>[]\n link?: Record<string, string>[]\n script?: ({ src?: string; children?: string } & Record<string, string | undefined>)[]\n style?: ({ children: string } & Record<string, string | undefined>)[]\n noscript?: { children: string }[]\n /** Convenience: emits a <script type=\"application/ld+json\"> tag with JSON.stringify'd content */\n jsonLd?: Record<string, unknown> | Record<string, unknown>[]\n base?: Record<string, string>\n /** Attributes to set on the <html> element (e.g. { lang: \"en\", dir: \"ltr\" }) */\n htmlAttrs?: Record<string, string>\n /** Attributes to set on the <body> element (e.g. { class: \"dark\" }) */\n bodyAttrs?: Record<string, string>\n}\n\n// ─── Context ──────────────────────────────────────────────────────────────────\n\nexport interface HeadEntry {\n tags: HeadTag[]\n titleTemplate?: string | ((title: string) => string) | undefined\n htmlAttrs?: Record<string, string> | undefined\n bodyAttrs?: Record<string, string> | undefined\n}\n\nexport interface HeadContextValue {\n add(id: symbol, entry: HeadEntry): void\n remove(id: symbol): void\n /** Returns deduplicated tags — last-added entry wins per key */\n resolve(): HeadTag[]\n /** Returns the merged titleTemplate (last-added wins) */\n resolveTitleTemplate(): (string | ((title: string) => string)) | undefined\n /** Returns merged htmlAttrs (later entries override earlier) */\n resolveHtmlAttrs(): Record<string, string>\n /** Returns merged bodyAttrs (later entries override earlier) */\n resolveBodyAttrs(): Record<string, string>\n}\n\nexport function createHeadContext(): HeadContextValue {\n const map = new Map<symbol, HeadEntry>()\n\n // ── Cached resolve ───────────────────────────────────────────────────────\n let dirty = true\n let cachedTags: HeadTag[] = []\n let cachedTitleTemplate: (string | ((title: string) => string)) | undefined\n let cachedHtmlAttrs: Record<string, string> = {}\n let cachedBodyAttrs: Record<string, string> = {}\n\n function rebuild(): void {\n if (!dirty) return\n dirty = false\n\n const keyed = new Map<string, HeadTag>()\n const unkeyed: HeadTag[] = []\n let titleTemplate: (string | ((title: string) => string)) | undefined\n const htmlAttrs: Record<string, string> = {}\n const bodyAttrs: Record<string, string> = {}\n\n for (const entry of map.values()) {\n for (const tag of entry.tags) {\n if (tag.key) keyed.set(tag.key, tag)\n else unkeyed.push(tag)\n }\n if (entry.titleTemplate !== undefined) titleTemplate = entry.titleTemplate\n if (entry.htmlAttrs) Object.assign(htmlAttrs, entry.htmlAttrs)\n if (entry.bodyAttrs) Object.assign(bodyAttrs, entry.bodyAttrs)\n }\n\n cachedTags = [...keyed.values(), ...unkeyed]\n cachedTitleTemplate = titleTemplate\n cachedHtmlAttrs = htmlAttrs\n cachedBodyAttrs = bodyAttrs\n }\n\n return {\n add(id, entry) {\n map.set(id, entry)\n dirty = true\n },\n remove(id) {\n map.delete(id)\n dirty = true\n },\n resolve() {\n rebuild()\n return cachedTags\n },\n resolveTitleTemplate() {\n rebuild()\n return cachedTitleTemplate\n },\n resolveHtmlAttrs() {\n rebuild()\n return cachedHtmlAttrs\n },\n resolveBodyAttrs() {\n rebuild()\n return cachedBodyAttrs\n },\n }\n}\n\nexport const HeadContext = createContext<HeadContextValue | null>(null)\n","import type { ComponentFn, Props, VNodeChild } from \"@pyreon/core\"\nimport { onUnmount, popContext, pushContext } from \"@pyreon/core\"\nimport type { HeadContextValue } from \"./context\"\nimport { createHeadContext, HeadContext } from \"./context\"\n\nexport interface HeadProviderProps extends Props {\n context?: HeadContextValue | undefined\n children?: VNodeChild\n}\n\n/**\n * Provides a HeadContextValue to all descendant components.\n * Wrap your app root with this to enable useHead() throughout the tree.\n *\n * If no `context` prop is passed, a new HeadContext is created automatically.\n *\n * @example\n * // Auto-create context:\n * <HeadProvider><App /></HeadProvider>\n *\n * // Explicit context (e.g. for SSR):\n * const headCtx = createHeadContext()\n * mount(h(HeadProvider, { context: headCtx }, h(App, null)), root)\n */\nexport const HeadProvider: ComponentFn<HeadProviderProps> = (props) => {\n const ctx = props.context ?? createHeadContext()\n // Push context frame synchronously (before children mount) so all descendants\n // can read HeadContext via useContext(). Pop on unmount for correct cleanup.\n const frame = new Map([[HeadContext.id, ctx]])\n pushContext(frame)\n onUnmount(() => popContext())\n\n const ch = props.children\n return (typeof ch === \"function\" ? (ch as () => VNodeChild)() : ch) as ReturnType<ComponentFn>\n}\n","import type { ComponentFn, VNode } from \"@pyreon/core\"\nimport { h, pushContext } from \"@pyreon/core\"\nimport { renderToString } from \"@pyreon/runtime-server\"\nimport type { HeadTag } from \"./context\"\nimport { createHeadContext, HeadContext } from \"./context\"\n\nconst VOID_TAGS = new Set([\"meta\", \"link\", \"base\"])\n\n/**\n * Render a Pyreon app to an HTML fragment + a serialized <head> string.\n *\n * The returned `head` string can be injected directly into your HTML template:\n *\n * @example\n * const { html, head } = await renderWithHead(h(App, null))\n * const page = `<!DOCTYPE html>\n * <html>\n * <head>\n * <meta charset=\"UTF-8\" />\n * ${head}\n * </head>\n * <body><div id=\"app\">${html}</div></body>\n * </html>`\n */\nexport interface RenderWithHeadResult {\n html: string\n head: string\n /** Attributes to set on the <html> element */\n htmlAttrs: Record<string, string>\n /** Attributes to set on the <body> element */\n bodyAttrs: Record<string, string>\n}\n\nexport async function renderWithHead(app: VNode): Promise<RenderWithHeadResult> {\n const ctx = createHeadContext()\n\n // HeadInjector runs inside renderToString's ALS scope, so pushContext reaches\n // the per-request context stack rather than the module-level fallback stack.\n function HeadInjector(): VNode {\n pushContext(new Map([[HeadContext.id, ctx]]))\n return app\n }\n\n const html = await renderToString(h(HeadInjector as ComponentFn, null))\n const titleTemplate = ctx.resolveTitleTemplate()\n const head = ctx\n .resolve()\n .map((tag) => serializeTag(tag, titleTemplate))\n .join(\"\\n \")\n return {\n html,\n head,\n htmlAttrs: ctx.resolveHtmlAttrs(),\n bodyAttrs: ctx.resolveBodyAttrs(),\n }\n}\n\nfunction serializeTag(tag: HeadTag, titleTemplate?: string | ((title: string) => string)): string {\n if (tag.tag === \"title\") {\n const raw = tag.children || \"\"\n const title = titleTemplate\n ? typeof titleTemplate === \"function\"\n ? titleTemplate(raw)\n : titleTemplate.replace(/%s/g, raw)\n : raw\n return `<title>${esc(title)}</title>`\n }\n const props = tag.props as Record<string, string> | undefined\n const attrs = props\n ? Object.entries(props)\n .map(([k, v]) => `${k}=\"${esc(v)}\"`)\n .join(\" \")\n : \"\"\n const open = attrs ? `<${tag.tag} ${attrs}` : `<${tag.tag}`\n if (VOID_TAGS.has(tag.tag)) return `${open} />`\n const content = tag.children || \"\"\n // Escape sequences that could break out of script/style/noscript blocks:\n // 1. Closing tags like </script> — use Unicode escape in the slash\n // 2. HTML comment openers <!-- that could confuse parsers\n const body = content.replace(/<\\/(script|style|noscript)/gi, \"<\\\\/$1\").replace(/<!--/g, \"<\\\\!--\")\n return `${open}>${body}</${tag.tag}>`\n}\n\nconst ESC_RE = /[&<>\"]/g\nconst ESC_MAP: Record<string, string> = { \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\" }\n\nfunction esc(s: string): string {\n return ESC_RE.test(s) ? s.replace(ESC_RE, (ch) => ESC_MAP[ch] as string) : s\n}\n","import type { HeadContextValue } from \"./context\"\n\nconst ATTR = \"data-pyreon-head\"\n\n/** Tracks managed elements by key — avoids querySelectorAll on every sync */\nconst managedElements = new Map<string, Element>()\n\n/**\n * Sync the resolved head tags to the real DOM <head>.\n * Uses incremental diffing: matches existing elements by key, patches attributes\n * in-place, adds new elements, and removes stale ones.\n * Also syncs htmlAttrs, bodyAttrs, and applies titleTemplate.\n * No-op on the server (typeof document === \"undefined\").\n */\nfunction patchExistingTag(\n found: Element,\n tag: { props: Record<string, unknown>; children: string },\n kept: Set<string>,\n): void {\n kept.add(found.getAttribute(ATTR) as string)\n patchAttrs(found, tag.props as Record<string, string>)\n const content = String(tag.children)\n if (found.textContent !== content) found.textContent = content\n}\n\nfunction createNewTag(tag: {\n tag: string\n props: Record<string, unknown>\n children: string\n key: unknown\n}): void {\n const el = document.createElement(tag.tag)\n const key = tag.key as string\n el.setAttribute(ATTR, key)\n for (const [k, v] of Object.entries(tag.props as Record<string, string>)) {\n el.setAttribute(k, v)\n }\n if (tag.children) el.textContent = tag.children\n document.head.appendChild(el)\n managedElements.set(key, el)\n}\n\nexport function syncDom(ctx: HeadContextValue): void {\n if (typeof document === \"undefined\") return\n\n const tags = ctx.resolve()\n const titleTemplate = ctx.resolveTitleTemplate()\n\n // Seed from DOM on first sync, or re-seed if DOM was reset (e.g. between tests)\n let needsSeed = managedElements.size === 0\n if (!needsSeed) {\n // Check if a tracked element is still in the DOM\n const sample = managedElements.values().next().value\n if (sample && !sample.isConnected) {\n managedElements.clear()\n needsSeed = true\n }\n }\n if (needsSeed) {\n const existing = document.head.querySelectorAll(`[${ATTR}]`)\n for (const el of existing) {\n managedElements.set(el.getAttribute(ATTR) as string, el)\n }\n }\n\n const kept = new Set<string>()\n\n for (const tag of tags) {\n if (tag.tag === \"title\") {\n document.title = applyTitleTemplate(String(tag.children), titleTemplate)\n continue\n }\n\n const key = tag.key as string\n const found = managedElements.get(key)\n\n if (found && found.tagName.toLowerCase() === tag.tag) {\n patchExistingTag(found, tag as { props: Record<string, unknown>; children: string }, kept)\n } else {\n if (found) {\n found.remove()\n managedElements.delete(key)\n }\n createNewTag(\n tag as { tag: string; props: Record<string, unknown>; children: string; key: unknown },\n )\n kept.add(key)\n }\n }\n\n // Remove stale elements\n for (const [key, el] of managedElements) {\n if (!kept.has(key)) {\n el.remove()\n managedElements.delete(key)\n }\n }\n\n syncElementAttrs(document.documentElement, ctx.resolveHtmlAttrs())\n syncElementAttrs(document.body, ctx.resolveBodyAttrs())\n}\n\n/** Patch an element's attributes to match the desired props. */\nfunction patchAttrs(el: Element, props: Record<string, string>): void {\n for (let i = el.attributes.length - 1; i >= 0; i--) {\n const attr = el.attributes[i]\n if (!attr || attr.name === ATTR) continue\n if (!(attr.name in props)) el.removeAttribute(attr.name)\n }\n for (const [k, v] of Object.entries(props)) {\n if (el.getAttribute(k) !== v) el.setAttribute(k, v)\n }\n}\n\nfunction applyTitleTemplate(\n title: string,\n template: string | ((t: string) => string) | undefined,\n): string {\n if (!template) return title\n if (typeof template === \"function\") return template(title)\n return template.replace(/%s/g, title)\n}\n\n/** Sync pyreon-managed attributes on <html> or <body>. */\nfunction syncElementAttrs(el: Element, attrs: Record<string, string>): void {\n // Remove previously managed attrs that are no longer present\n const managed = el.getAttribute(`${ATTR}-attrs`)\n if (managed) {\n for (const name of managed.split(\",\")) {\n if (name && !(name in attrs)) el.removeAttribute(name)\n }\n }\n const keys: string[] = []\n for (const [k, v] of Object.entries(attrs)) {\n keys.push(k)\n if (el.getAttribute(k) !== v) el.setAttribute(k, v)\n }\n if (keys.length > 0) {\n el.setAttribute(`${ATTR}-attrs`, keys.join(\",\"))\n } else if (managed) {\n el.removeAttribute(`${ATTR}-attrs`)\n }\n}\n","import { onMount, onUnmount, useContext } from \"@pyreon/core\"\nimport { effect } from \"@pyreon/reactivity\"\nimport type { HeadEntry, HeadTag, UseHeadInput } from \"./context\"\nimport { HeadContext } from \"./context\"\nimport { syncDom } from \"./dom\"\n\nfunction buildEntry(o: UseHeadInput): HeadEntry {\n const tags: HeadTag[] = []\n if (o.title != null) tags.push({ tag: \"title\", key: \"title\", children: o.title })\n o.meta?.forEach((m, i) => {\n tags.push({\n tag: \"meta\",\n key: m.name ?? m.property ?? `meta-${i}`,\n props: m,\n })\n })\n o.link?.forEach((l, i) => {\n tags.push({\n tag: \"link\",\n key: l.href ? `link-${l.rel || \"\"}-${l.href}` : l.rel ? `link-${l.rel}` : `link-${i}`,\n props: l,\n })\n })\n o.script?.forEach((s, i) => {\n const { children, ...rest } = s\n tags.push({\n tag: \"script\",\n key: s.src ?? `script-${i}`,\n props: rest as Record<string, string>,\n ...(children != null ? { children } : {}),\n })\n })\n o.style?.forEach((s, i) => {\n const { children, ...rest } = s\n tags.push({\n tag: \"style\",\n key: `style-${i}`,\n props: rest as Record<string, string>,\n children,\n })\n })\n o.noscript?.forEach((ns, i) => {\n tags.push({ tag: \"noscript\", key: `noscript-${i}`, children: ns.children })\n })\n if (o.jsonLd) {\n tags.push({\n tag: \"script\",\n key: \"jsonld\",\n props: { type: \"application/ld+json\" },\n children: JSON.stringify(o.jsonLd),\n })\n }\n if (o.base) tags.push({ tag: \"base\", key: \"base\", props: o.base })\n return {\n tags,\n titleTemplate: o.titleTemplate,\n htmlAttrs: o.htmlAttrs,\n bodyAttrs: o.bodyAttrs,\n }\n}\n\n/**\n * Register head tags (title, meta, link, script, style, noscript, base, jsonLd)\n * for the current component.\n *\n * Accepts a static object or a reactive getter:\n * useHead({ title: \"My Page\", meta: [{ name: \"description\", content: \"...\" }] })\n * useHead(() => ({ title: `${count()} items` })) // updates when signal changes\n *\n * Tags are deduplicated by key — innermost component wins.\n * Requires a <HeadProvider> (CSR) or renderWithHead() (SSR) ancestor.\n */\nexport function useHead(input: UseHeadInput | (() => UseHeadInput)): void {\n const ctx = useContext(HeadContext)\n if (!ctx) return // no HeadProvider — silently no-op\n\n const id = Symbol()\n\n if (typeof input === \"function\") {\n if (typeof document !== \"undefined\") {\n // CSR: reactive — re-register whenever signals change\n effect(() => {\n ctx.add(id, buildEntry(input()))\n syncDom(ctx)\n })\n } else {\n // SSR: evaluate once synchronously (no effects on server)\n ctx.add(id, buildEntry(input()))\n }\n } else {\n ctx.add(id, buildEntry(input))\n onMount(() => {\n syncDom(ctx)\n return undefined\n })\n }\n\n onUnmount(() => {\n ctx.remove(id)\n syncDom(ctx)\n })\n}\n"],"mappings":";;;;;AA+DA,SAAgB,oBAAsC;CACpD,MAAM,sBAAM,IAAI,KAAwB;CAGxC,IAAI,QAAQ;CACZ,IAAI,aAAwB,EAAE;CAC9B,IAAI;CACJ,IAAI,kBAA0C,EAAE;CAChD,IAAI,kBAA0C,EAAE;CAEhD,SAAS,UAAgB;AACvB,MAAI,CAAC,MAAO;AACZ,UAAQ;EAER,MAAM,wBAAQ,IAAI,KAAsB;EACxC,MAAM,UAAqB,EAAE;EAC7B,IAAI;EACJ,MAAM,YAAoC,EAAE;EAC5C,MAAM,YAAoC,EAAE;AAE5C,OAAK,MAAM,SAAS,IAAI,QAAQ,EAAE;AAChC,QAAK,MAAM,OAAO,MAAM,KACtB,KAAI,IAAI,IAAK,OAAM,IAAI,IAAI,KAAK,IAAI;OAC/B,SAAQ,KAAK,IAAI;AAExB,OAAI,MAAM,kBAAkB,OAAW,iBAAgB,MAAM;AAC7D,OAAI,MAAM,UAAW,QAAO,OAAO,WAAW,MAAM,UAAU;AAC9D,OAAI,MAAM,UAAW,QAAO,OAAO,WAAW,MAAM,UAAU;;AAGhE,eAAa,CAAC,GAAG,MAAM,QAAQ,EAAE,GAAG,QAAQ;AAC5C,wBAAsB;AACtB,oBAAkB;AAClB,oBAAkB;;AAGpB,QAAO;EACL,IAAI,IAAI,OAAO;AACb,OAAI,IAAI,IAAI,MAAM;AAClB,WAAQ;;EAEV,OAAO,IAAI;AACT,OAAI,OAAO,GAAG;AACd,WAAQ;;EAEV,UAAU;AACR,YAAS;AACT,UAAO;;EAET,uBAAuB;AACrB,YAAS;AACT,UAAO;;EAET,mBAAmB;AACjB,YAAS;AACT,UAAO;;EAET,mBAAmB;AACjB,YAAS;AACT,UAAO;;EAEV;;AAGH,MAAa,cAAc,cAAuC,KAAK;;;;;;;;;;;;;;;;;;ACvGvE,MAAa,gBAAgD,UAAU;CACrE,MAAM,MAAM,MAAM,WAAW,mBAAmB;AAIhD,aADc,IAAI,IAAI,CAAC,CAAC,YAAY,IAAI,IAAI,CAAC,CAAC,CAC5B;AAClB,iBAAgB,YAAY,CAAC;CAE7B,MAAM,KAAK,MAAM;AACjB,QAAQ,OAAO,OAAO,aAAc,IAAyB,GAAG;;;;;AC3BlE,MAAM,YAAY,IAAI,IAAI;CAAC;CAAQ;CAAQ;CAAO,CAAC;AA2BnD,eAAsB,eAAe,KAA2C;CAC9E,MAAM,MAAM,mBAAmB;CAI/B,SAAS,eAAsB;AAC7B,cAAY,IAAI,IAAI,CAAC,CAAC,YAAY,IAAI,IAAI,CAAC,CAAC,CAAC;AAC7C,SAAO;;CAGT,MAAM,OAAO,MAAM,eAAe,EAAE,cAA6B,KAAK,CAAC;CACvE,MAAM,gBAAgB,IAAI,sBAAsB;AAKhD,QAAO;EACL;EACA,MANW,IACV,SAAS,CACT,KAAK,QAAQ,aAAa,KAAK,cAAc,CAAC,CAC9C,KAAK,OAAO;EAIb,WAAW,IAAI,kBAAkB;EACjC,WAAW,IAAI,kBAAkB;EAClC;;AAGH,SAAS,aAAa,KAAc,eAA8D;AAChG,KAAI,IAAI,QAAQ,SAAS;EACvB,MAAM,MAAM,IAAI,YAAY;AAM5B,SAAO,UAAU,IALH,gBACV,OAAO,kBAAkB,aACvB,cAAc,IAAI,GAClB,cAAc,QAAQ,OAAO,IAAI,GACnC,IACuB,CAAC;;CAE9B,MAAM,QAAQ,IAAI;CAClB,MAAM,QAAQ,QACV,OAAO,QAAQ,MAAM,CAClB,KAAK,CAAC,GAAG,OAAO,GAAG,EAAE,IAAI,IAAI,EAAE,CAAC,GAAG,CACnC,KAAK,IAAI,GACZ;CACJ,MAAM,OAAO,QAAQ,IAAI,IAAI,IAAI,GAAG,UAAU,IAAI,IAAI;AACtD,KAAI,UAAU,IAAI,IAAI,IAAI,CAAE,QAAO,GAAG,KAAK;AAM3C,QAAO,GAAG,KAAK,IALC,IAAI,YAAY,IAIX,QAAQ,gCAAgC,SAAS,CAAC,QAAQ,SAAS,SAAS,CAC1E,IAAI,IAAI,IAAI;;AAGrC,MAAM,SAAS;AACf,MAAM,UAAkC;CAAE,KAAK;CAAS,KAAK;CAAQ,KAAK;CAAQ,MAAK;CAAU;AAEjG,SAAS,IAAI,GAAmB;AAC9B,QAAO,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,SAAS,OAAO,QAAQ,IAAc,GAAG;;;;;ACrF7E,MAAM,OAAO;;AAGb,MAAM,kCAAkB,IAAI,KAAsB;;;;;;;;AASlD,SAAS,iBACP,OACA,KACA,MACM;AACN,MAAK,IAAI,MAAM,aAAa,KAAK,CAAW;AAC5C,YAAW,OAAO,IAAI,MAAgC;CACtD,MAAM,UAAU,OAAO,IAAI,SAAS;AACpC,KAAI,MAAM,gBAAgB,QAAS,OAAM,cAAc;;AAGzD,SAAS,aAAa,KAKb;CACP,MAAM,KAAK,SAAS,cAAc,IAAI,IAAI;CAC1C,MAAM,MAAM,IAAI;AAChB,IAAG,aAAa,MAAM,IAAI;AAC1B,MAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,IAAI,MAAgC,CACtE,IAAG,aAAa,GAAG,EAAE;AAEvB,KAAI,IAAI,SAAU,IAAG,cAAc,IAAI;AACvC,UAAS,KAAK,YAAY,GAAG;AAC7B,iBAAgB,IAAI,KAAK,GAAG;;AAG9B,SAAgB,QAAQ,KAA6B;AACnD,KAAI,OAAO,aAAa,YAAa;CAErC,MAAM,OAAO,IAAI,SAAS;CAC1B,MAAM,gBAAgB,IAAI,sBAAsB;CAGhD,IAAI,YAAY,gBAAgB,SAAS;AACzC,KAAI,CAAC,WAAW;EAEd,MAAM,SAAS,gBAAgB,QAAQ,CAAC,MAAM,CAAC;AAC/C,MAAI,UAAU,CAAC,OAAO,aAAa;AACjC,mBAAgB,OAAO;AACvB,eAAY;;;AAGhB,KAAI,WAAW;EACb,MAAM,WAAW,SAAS,KAAK,iBAAiB,IAAI,KAAK,GAAG;AAC5D,OAAK,MAAM,MAAM,SACf,iBAAgB,IAAI,GAAG,aAAa,KAAK,EAAY,GAAG;;CAI5D,MAAM,uBAAO,IAAI,KAAa;AAE9B,MAAK,MAAM,OAAO,MAAM;AACtB,MAAI,IAAI,QAAQ,SAAS;AACvB,YAAS,QAAQ,mBAAmB,OAAO,IAAI,SAAS,EAAE,cAAc;AACxE;;EAGF,MAAM,MAAM,IAAI;EAChB,MAAM,QAAQ,gBAAgB,IAAI,IAAI;AAEtC,MAAI,SAAS,MAAM,QAAQ,aAAa,KAAK,IAAI,IAC/C,kBAAiB,OAAO,KAA6D,KAAK;OACrF;AACL,OAAI,OAAO;AACT,UAAM,QAAQ;AACd,oBAAgB,OAAO,IAAI;;AAE7B,gBACE,IACD;AACD,QAAK,IAAI,IAAI;;;AAKjB,MAAK,MAAM,CAAC,KAAK,OAAO,gBACtB,KAAI,CAAC,KAAK,IAAI,IAAI,EAAE;AAClB,KAAG,QAAQ;AACX,kBAAgB,OAAO,IAAI;;AAI/B,kBAAiB,SAAS,iBAAiB,IAAI,kBAAkB,CAAC;AAClE,kBAAiB,SAAS,MAAM,IAAI,kBAAkB,CAAC;;;AAIzD,SAAS,WAAW,IAAa,OAAqC;AACpE,MAAK,IAAI,IAAI,GAAG,WAAW,SAAS,GAAG,KAAK,GAAG,KAAK;EAClD,MAAM,OAAO,GAAG,WAAW;AAC3B,MAAI,CAAC,QAAQ,KAAK,SAAS,KAAM;AACjC,MAAI,EAAE,KAAK,QAAQ,OAAQ,IAAG,gBAAgB,KAAK,KAAK;;AAE1D,MAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,MAAM,CACxC,KAAI,GAAG,aAAa,EAAE,KAAK,EAAG,IAAG,aAAa,GAAG,EAAE;;AAIvD,SAAS,mBACP,OACA,UACQ;AACR,KAAI,CAAC,SAAU,QAAO;AACtB,KAAI,OAAO,aAAa,WAAY,QAAO,SAAS,MAAM;AAC1D,QAAO,SAAS,QAAQ,OAAO,MAAM;;;AAIvC,SAAS,iBAAiB,IAAa,OAAqC;CAE1E,MAAM,UAAU,GAAG,aAAa,GAAG,KAAK,QAAQ;AAChD,KAAI,SACF;OAAK,MAAM,QAAQ,QAAQ,MAAM,IAAI,CACnC,KAAI,QAAQ,EAAE,QAAQ,OAAQ,IAAG,gBAAgB,KAAK;;CAG1D,MAAM,OAAiB,EAAE;AACzB,MAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,MAAM,EAAE;AAC1C,OAAK,KAAK,EAAE;AACZ,MAAI,GAAG,aAAa,EAAE,KAAK,EAAG,IAAG,aAAa,GAAG,EAAE;;AAErD,KAAI,KAAK,SAAS,EAChB,IAAG,aAAa,GAAG,KAAK,SAAS,KAAK,KAAK,IAAI,CAAC;UACvC,QACT,IAAG,gBAAgB,GAAG,KAAK,QAAQ;;;;;ACtIvC,SAAS,WAAW,GAA4B;CAC9C,MAAM,OAAkB,EAAE;AAC1B,KAAI,EAAE,SAAS,KAAM,MAAK,KAAK;EAAE,KAAK;EAAS,KAAK;EAAS,UAAU,EAAE;EAAO,CAAC;AACjF,GAAE,MAAM,SAAS,GAAG,MAAM;AACxB,OAAK,KAAK;GACR,KAAK;GACL,KAAK,EAAE,QAAQ,EAAE,YAAY,QAAQ;GACrC,OAAO;GACR,CAAC;GACF;AACF,GAAE,MAAM,SAAS,GAAG,MAAM;AACxB,OAAK,KAAK;GACR,KAAK;GACL,KAAK,EAAE,OAAO,QAAQ,EAAE,OAAO,GAAG,GAAG,EAAE,SAAS,EAAE,MAAM,QAAQ,EAAE,QAAQ,QAAQ;GAClF,OAAO;GACR,CAAC;GACF;AACF,GAAE,QAAQ,SAAS,GAAG,MAAM;EAC1B,MAAM,EAAE,UAAU,GAAG,SAAS;AAC9B,OAAK,KAAK;GACR,KAAK;GACL,KAAK,EAAE,OAAO,UAAU;GACxB,OAAO;GACP,GAAI,YAAY,OAAO,EAAE,UAAU,GAAG,EAAE;GACzC,CAAC;GACF;AACF,GAAE,OAAO,SAAS,GAAG,MAAM;EACzB,MAAM,EAAE,UAAU,GAAG,SAAS;AAC9B,OAAK,KAAK;GACR,KAAK;GACL,KAAK,SAAS;GACd,OAAO;GACP;GACD,CAAC;GACF;AACF,GAAE,UAAU,SAAS,IAAI,MAAM;AAC7B,OAAK,KAAK;GAAE,KAAK;GAAY,KAAK,YAAY;GAAK,UAAU,GAAG;GAAU,CAAC;GAC3E;AACF,KAAI,EAAE,OACJ,MAAK,KAAK;EACR,KAAK;EACL,KAAK;EACL,OAAO,EAAE,MAAM,uBAAuB;EACtC,UAAU,KAAK,UAAU,EAAE,OAAO;EACnC,CAAC;AAEJ,KAAI,EAAE,KAAM,MAAK,KAAK;EAAE,KAAK;EAAQ,KAAK;EAAQ,OAAO,EAAE;EAAM,CAAC;AAClE,QAAO;EACL;EACA,eAAe,EAAE;EACjB,WAAW,EAAE;EACb,WAAW,EAAE;EACd;;;;;;;;;;;;;AAcH,SAAgB,QAAQ,OAAkD;CACxE,MAAM,MAAM,WAAW,YAAY;AACnC,KAAI,CAAC,IAAK;CAEV,MAAM,KAAK,QAAQ;AAEnB,KAAI,OAAO,UAAU,WACnB,KAAI,OAAO,aAAa,YAEtB,cAAa;AACX,MAAI,IAAI,IAAI,WAAW,OAAO,CAAC,CAAC;AAChC,UAAQ,IAAI;GACZ;KAGF,KAAI,IAAI,IAAI,WAAW,OAAO,CAAC,CAAC;MAE7B;AACL,MAAI,IAAI,IAAI,WAAW,MAAM,CAAC;AAC9B,gBAAc;AACZ,WAAQ,IAAI;IAEZ;;AAGJ,iBAAgB;AACd,MAAI,OAAO,GAAG;AACd,UAAQ,IAAI;GACZ"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/context.ts","../src/provider.ts","../src/ssr.ts","../src/dom.ts","../src/use-head.ts"],"sourcesContent":["import { createContext } from \"@pyreon/core\"\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface HeadTag {\n /** HTML tag name */\n tag: \"title\" | \"meta\" | \"link\" | \"script\" | \"style\" | \"base\" | \"noscript\"\n /**\n * Deduplication key. Tags with the same key replace each other;\n * innermost component (last added) wins.\n * Example: all components setting the page title use key \"title\".\n */\n key?: string\n /** HTML attributes for the tag */\n props?: Record<string, string>\n /** Text content — for <title>, <script>, <style>, <noscript> */\n children?: string\n}\n\n// ─── Strict tag types ────────────────────────────────────────────────────────\n\n/** Standard `<meta>` tag attributes. Catches typos like `{ naem: \"description\" }`. */\nexport interface MetaTag {\n /** Standard meta name (e.g. \"description\", \"viewport\", \"robots\") */\n name?: string\n /** Open Graph / social property (e.g. \"og:title\", \"twitter:card\") */\n property?: string\n /** HTTP equivalent header (e.g. \"refresh\", \"content-type\") */\n \"http-equiv\"?: string\n /** Value associated with name, property, or http-equiv */\n content?: string\n /** Document character encoding (e.g. \"utf-8\") */\n charset?: string\n /** Schema.org itemprop */\n itemprop?: string\n /** Media condition for applicability (e.g. \"(prefers-color-scheme: dark)\") */\n media?: string\n}\n\n/** Standard `<link>` tag attributes. */\nexport interface LinkTag {\n /** Relationship to the current document (e.g. \"stylesheet\", \"icon\", \"canonical\") */\n rel?: string\n /** URL of the linked resource */\n href?: string\n /** Resource type hint for preloading (e.g. \"style\", \"script\", \"font\") */\n as?: string\n /** MIME type (e.g. \"text/css\", \"image/png\") */\n type?: string\n /** Media query for conditional loading */\n media?: string\n /** CORS mode */\n crossorigin?: string\n /** Subresource integrity hash */\n integrity?: string\n /** Icon sizes (e.g. \"32x32\", \"any\") */\n sizes?: string\n /** Language of the linked resource */\n hreflang?: string\n /** Title for the link (used for alternate stylesheets) */\n title?: string\n /** Fetch priority hint */\n fetchpriority?: \"high\" | \"low\" | \"auto\"\n /** Referrer policy */\n referrerpolicy?: string\n /** Image source set for preloading responsive images */\n imagesrcset?: string\n /** Image sizes for preloading responsive images */\n imagesizes?: string\n /** Disable the resource (for stylesheets) */\n disabled?: string\n /** Color for mask-icon */\n color?: string\n}\n\n/** Standard `<script>` tag attributes. */\nexport interface ScriptTag {\n /** External script URL */\n src?: string\n /** Script MIME type or module type (e.g. \"module\", \"importmap\") */\n type?: string\n /** Load asynchronously */\n async?: string\n /** Defer execution until document is parsed */\n defer?: string\n /** CORS mode */\n crossorigin?: string\n /** Subresource integrity hash */\n integrity?: string\n /** Exclude from module-supporting browsers */\n nomodule?: string\n /** Referrer policy */\n referrerpolicy?: string\n /** Fetch priority hint */\n fetchpriority?: string\n /** Inline script content */\n children?: string\n}\n\n/** Standard `<style>` tag attributes. */\nexport interface StyleTag {\n /** Inline CSS content (required) */\n children: string\n /** Media query for conditional styles */\n media?: string\n /** Nonce for CSP */\n nonce?: string\n /** Title for alternate stylesheets */\n title?: string\n /** Render-blocking behavior */\n blocking?: string\n}\n\n/** Standard `<base>` tag attributes. */\nexport interface BaseTag {\n /** Base URL for relative URLs in the document */\n href?: string\n /** Default target for links and forms */\n target?: \"_blank\" | \"_self\" | \"_parent\" | \"_top\"\n}\n\nexport interface UseHeadInput {\n title?: string\n /**\n * Title template — use `%s` as a placeholder for the page title.\n * Applied to the resolved title after deduplication.\n * @example useHead({ titleTemplate: \"%s | My App\" })\n */\n titleTemplate?: string | ((title: string) => string)\n meta?: MetaTag[]\n link?: LinkTag[]\n script?: ScriptTag[]\n style?: StyleTag[]\n noscript?: { children: string }[]\n /** Convenience: emits a <script type=\"application/ld+json\"> tag with JSON.stringify'd content */\n jsonLd?: Record<string, unknown> | Record<string, unknown>[]\n base?: BaseTag\n /** Attributes to set on the <html> element (e.g. { lang: \"en\", dir: \"ltr\" }) */\n htmlAttrs?: Record<string, string>\n /** Attributes to set on the <body> element (e.g. { class: \"dark\" }) */\n bodyAttrs?: Record<string, string>\n}\n\n// ─── Context ──────────────────────────────────────────────────────────────────\n\nexport interface HeadEntry {\n tags: HeadTag[]\n titleTemplate?: string | ((title: string) => string) | undefined\n htmlAttrs?: Record<string, string> | undefined\n bodyAttrs?: Record<string, string> | undefined\n}\n\nexport interface HeadContextValue {\n add(id: symbol, entry: HeadEntry): void\n remove(id: symbol): void\n /** Returns deduplicated tags — last-added entry wins per key */\n resolve(): HeadTag[]\n /** Returns the merged titleTemplate (last-added wins) */\n resolveTitleTemplate(): (string | ((title: string) => string)) | undefined\n /** Returns merged htmlAttrs (later entries override earlier) */\n resolveHtmlAttrs(): Record<string, string>\n /** Returns merged bodyAttrs (later entries override earlier) */\n resolveBodyAttrs(): Record<string, string>\n}\n\nexport function createHeadContext(): HeadContextValue {\n const map = new Map<symbol, HeadEntry>()\n\n // ── Cached resolve ───────────────────────────────────────────────────────\n let dirty = true\n let cachedTags: HeadTag[] = []\n let cachedTitleTemplate: (string | ((title: string) => string)) | undefined\n let cachedHtmlAttrs: Record<string, string> = {}\n let cachedBodyAttrs: Record<string, string> = {}\n\n function rebuild(): void {\n if (!dirty) return\n dirty = false\n\n const keyed = new Map<string, HeadTag>()\n const unkeyed: HeadTag[] = []\n let titleTemplate: (string | ((title: string) => string)) | undefined\n const htmlAttrs: Record<string, string> = {}\n const bodyAttrs: Record<string, string> = {}\n\n for (const entry of map.values()) {\n for (const tag of entry.tags) {\n if (tag.key) keyed.set(tag.key, tag)\n else unkeyed.push(tag)\n }\n if (entry.titleTemplate !== undefined) titleTemplate = entry.titleTemplate\n if (entry.htmlAttrs) Object.assign(htmlAttrs, entry.htmlAttrs)\n if (entry.bodyAttrs) Object.assign(bodyAttrs, entry.bodyAttrs)\n }\n\n cachedTags = [...keyed.values(), ...unkeyed]\n cachedTitleTemplate = titleTemplate\n cachedHtmlAttrs = htmlAttrs\n cachedBodyAttrs = bodyAttrs\n }\n\n return {\n add(id, entry) {\n map.set(id, entry)\n dirty = true\n },\n remove(id) {\n map.delete(id)\n dirty = true\n },\n resolve() {\n rebuild()\n return cachedTags\n },\n resolveTitleTemplate() {\n rebuild()\n return cachedTitleTemplate\n },\n resolveHtmlAttrs() {\n rebuild()\n return cachedHtmlAttrs\n },\n resolveBodyAttrs() {\n rebuild()\n return cachedBodyAttrs\n },\n }\n}\n\nexport const HeadContext = createContext<HeadContextValue | null>(null)\n","import type { ComponentFn, Props, VNodeChild } from \"@pyreon/core\"\nimport { onUnmount, popContext, pushContext } from \"@pyreon/core\"\nimport type { HeadContextValue } from \"./context\"\nimport { createHeadContext, HeadContext } from \"./context\"\n\nexport interface HeadProviderProps extends Props {\n context?: HeadContextValue | undefined\n children?: VNodeChild\n}\n\n/**\n * Provides a HeadContextValue to all descendant components.\n * Wrap your app root with this to enable useHead() throughout the tree.\n *\n * If no `context` prop is passed, a new HeadContext is created automatically.\n *\n * @example\n * // Auto-create context:\n * <HeadProvider><App /></HeadProvider>\n *\n * // Explicit context (e.g. for SSR):\n * const headCtx = createHeadContext()\n * mount(h(HeadProvider, { context: headCtx }, h(App, null)), root)\n */\nexport const HeadProvider: ComponentFn<HeadProviderProps> = (props) => {\n const ctx = props.context ?? createHeadContext()\n // Push context frame synchronously (before children mount) so all descendants\n // can read HeadContext via useContext(). Pop on unmount for correct cleanup.\n const frame = new Map([[HeadContext.id, ctx]])\n pushContext(frame)\n onUnmount(() => popContext())\n\n const ch = props.children\n return (typeof ch === \"function\" ? (ch as () => VNodeChild)() : ch) as ReturnType<ComponentFn>\n}\n","import type { ComponentFn, VNode } from \"@pyreon/core\"\nimport { h, pushContext } from \"@pyreon/core\"\nimport { renderToString } from \"@pyreon/runtime-server\"\nimport type { HeadTag } from \"./context\"\nimport { createHeadContext, HeadContext } from \"./context\"\n\nconst VOID_TAGS = new Set([\"meta\", \"link\", \"base\"])\n\n/**\n * Render a Pyreon app to an HTML fragment + a serialized <head> string.\n *\n * The returned `head` string can be injected directly into your HTML template:\n *\n * @example\n * const { html, head } = await renderWithHead(h(App, null))\n * const page = `<!DOCTYPE html>\n * <html>\n * <head>\n * <meta charset=\"UTF-8\" />\n * ${head}\n * </head>\n * <body><div id=\"app\">${html}</div></body>\n * </html>`\n */\nexport interface RenderWithHeadResult {\n html: string\n head: string\n /** Attributes to set on the <html> element */\n htmlAttrs: Record<string, string>\n /** Attributes to set on the <body> element */\n bodyAttrs: Record<string, string>\n}\n\nexport async function renderWithHead(app: VNode): Promise<RenderWithHeadResult> {\n const ctx = createHeadContext()\n\n // HeadInjector runs inside renderToString's ALS scope, so pushContext reaches\n // the per-request context stack rather than the module-level fallback stack.\n function HeadInjector(): VNode {\n pushContext(new Map([[HeadContext.id, ctx]]))\n return app\n }\n\n const html = await renderToString(h(HeadInjector as ComponentFn, null))\n const titleTemplate = ctx.resolveTitleTemplate()\n const head = ctx\n .resolve()\n .map((tag) => serializeTag(tag, titleTemplate))\n .join(\"\\n \")\n return {\n html,\n head,\n htmlAttrs: ctx.resolveHtmlAttrs(),\n bodyAttrs: ctx.resolveBodyAttrs(),\n }\n}\n\nfunction serializeTag(tag: HeadTag, titleTemplate?: string | ((title: string) => string)): string {\n if (tag.tag === \"title\") {\n const raw = tag.children || \"\"\n const title = titleTemplate\n ? typeof titleTemplate === \"function\"\n ? titleTemplate(raw)\n : titleTemplate.replace(/%s/g, raw)\n : raw\n return `<title>${esc(title)}</title>`\n }\n const props = tag.props as Record<string, string> | undefined\n const attrs = props\n ? Object.entries(props)\n .map(([k, v]) => `${k}=\"${esc(v)}\"`)\n .join(\" \")\n : \"\"\n const open = attrs ? `<${tag.tag} ${attrs}` : `<${tag.tag}`\n if (VOID_TAGS.has(tag.tag)) return `${open} />`\n const content = tag.children || \"\"\n // Escape sequences that could break out of script/style/noscript blocks:\n // 1. Closing tags like </script> — use Unicode escape in the slash\n // 2. HTML comment openers <!-- that could confuse parsers\n const body = content.replace(/<\\/(script|style|noscript)/gi, \"<\\\\/$1\").replace(/<!--/g, \"<\\\\!--\")\n return `${open}>${body}</${tag.tag}>`\n}\n\nconst ESC_RE = /[&<>\"]/g\nconst ESC_MAP: Record<string, string> = { \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\" }\n\nfunction esc(s: string): string {\n return ESC_RE.test(s) ? s.replace(ESC_RE, (ch) => ESC_MAP[ch] as string) : s\n}\n","import type { HeadContextValue } from \"./context\"\n\nconst ATTR = \"data-pyreon-head\"\n\n/** Tracks managed elements by key — avoids querySelectorAll on every sync */\nconst managedElements = new Map<string, Element>()\n\n/**\n * Sync the resolved head tags to the real DOM <head>.\n * Uses incremental diffing: matches existing elements by key, patches attributes\n * in-place, adds new elements, and removes stale ones.\n * Also syncs htmlAttrs, bodyAttrs, and applies titleTemplate.\n * No-op on the server (typeof document === \"undefined\").\n */\nfunction patchExistingTag(\n found: Element,\n tag: { props: Record<string, unknown>; children: string },\n kept: Set<string>,\n): void {\n kept.add(found.getAttribute(ATTR) as string)\n patchAttrs(found, tag.props as Record<string, string>)\n const content = String(tag.children)\n if (found.textContent !== content) found.textContent = content\n}\n\nfunction createNewTag(tag: {\n tag: string\n props: Record<string, unknown>\n children: string\n key: unknown\n}): void {\n const el = document.createElement(tag.tag)\n const key = tag.key as string\n el.setAttribute(ATTR, key)\n for (const [k, v] of Object.entries(tag.props as Record<string, string>)) {\n el.setAttribute(k, v)\n }\n if (tag.children) el.textContent = tag.children\n document.head.appendChild(el)\n managedElements.set(key, el)\n}\n\nexport function syncDom(ctx: HeadContextValue): void {\n if (typeof document === \"undefined\") return\n\n const tags = ctx.resolve()\n const titleTemplate = ctx.resolveTitleTemplate()\n\n // Seed from DOM on first sync, or re-seed if DOM was reset (e.g. between tests)\n let needsSeed = managedElements.size === 0\n if (!needsSeed) {\n // Check if a tracked element is still in the DOM\n const sample = managedElements.values().next().value\n if (sample && !sample.isConnected) {\n managedElements.clear()\n needsSeed = true\n }\n }\n if (needsSeed) {\n const existing = document.head.querySelectorAll(`[${ATTR}]`)\n for (const el of existing) {\n managedElements.set(el.getAttribute(ATTR) as string, el)\n }\n }\n\n const kept = new Set<string>()\n\n for (const tag of tags) {\n if (tag.tag === \"title\") {\n document.title = applyTitleTemplate(String(tag.children), titleTemplate)\n continue\n }\n\n const key = tag.key as string\n const found = managedElements.get(key)\n\n if (found && found.tagName.toLowerCase() === tag.tag) {\n patchExistingTag(found, tag as { props: Record<string, unknown>; children: string }, kept)\n } else {\n if (found) {\n found.remove()\n managedElements.delete(key)\n }\n createNewTag(\n tag as { tag: string; props: Record<string, unknown>; children: string; key: unknown },\n )\n kept.add(key)\n }\n }\n\n // Remove stale elements\n for (const [key, el] of managedElements) {\n if (!kept.has(key)) {\n el.remove()\n managedElements.delete(key)\n }\n }\n\n syncElementAttrs(document.documentElement, ctx.resolveHtmlAttrs())\n syncElementAttrs(document.body, ctx.resolveBodyAttrs())\n}\n\n/** Patch an element's attributes to match the desired props. */\nfunction patchAttrs(el: Element, props: Record<string, string>): void {\n for (let i = el.attributes.length - 1; i >= 0; i--) {\n const attr = el.attributes[i]\n if (!attr || attr.name === ATTR) continue\n if (!(attr.name in props)) el.removeAttribute(attr.name)\n }\n for (const [k, v] of Object.entries(props)) {\n if (el.getAttribute(k) !== v) el.setAttribute(k, v)\n }\n}\n\nfunction applyTitleTemplate(\n title: string,\n template: string | ((t: string) => string) | undefined,\n): string {\n if (!template) return title\n if (typeof template === \"function\") return template(title)\n return template.replace(/%s/g, title)\n}\n\n/** Sync pyreon-managed attributes on <html> or <body>. */\nfunction syncElementAttrs(el: Element, attrs: Record<string, string>): void {\n // Remove previously managed attrs that are no longer present\n const managed = el.getAttribute(`${ATTR}-attrs`)\n if (managed) {\n for (const name of managed.split(\",\")) {\n if (name && !(name in attrs)) el.removeAttribute(name)\n }\n }\n const keys: string[] = []\n for (const [k, v] of Object.entries(attrs)) {\n keys.push(k)\n if (el.getAttribute(k) !== v) el.setAttribute(k, v)\n }\n if (keys.length > 0) {\n el.setAttribute(`${ATTR}-attrs`, keys.join(\",\"))\n } else if (managed) {\n el.removeAttribute(`${ATTR}-attrs`)\n }\n}\n","import { onMount, onUnmount, useContext } from \"@pyreon/core\"\nimport { effect } from \"@pyreon/reactivity\"\nimport type { HeadEntry, HeadTag, UseHeadInput } from \"./context\"\nimport { HeadContext } from \"./context\"\nimport { syncDom } from \"./dom\"\n\n/** Cast a strict tag interface to the internal props format, stripping undefined values */\nfunction toProps(obj: Record<string, string | undefined>): Record<string, string> {\n const result: Record<string, string> = {}\n for (const [k, v] of Object.entries(obj)) {\n if (v !== undefined) result[k] = v\n }\n return result\n}\n\nfunction buildEntry(o: UseHeadInput): HeadEntry {\n const tags: HeadTag[] = []\n if (o.title != null) tags.push({ tag: \"title\", key: \"title\", children: o.title })\n o.meta?.forEach((m, i) => {\n tags.push({\n tag: \"meta\",\n key: m.name ?? m.property ?? `meta-${i}`,\n props: toProps(m as Record<string, string | undefined>),\n })\n })\n o.link?.forEach((l, i) => {\n tags.push({\n tag: \"link\",\n key: l.href ? `link-${l.rel || \"\"}-${l.href}` : l.rel ? `link-${l.rel}` : `link-${i}`,\n props: toProps(l as Record<string, string | undefined>),\n })\n })\n o.script?.forEach((s, i) => {\n const { children, ...rest } = s\n tags.push({\n tag: \"script\",\n key: s.src ?? `script-${i}`,\n props: toProps(rest as Record<string, string | undefined>),\n ...(children != null ? { children } : {}),\n })\n })\n o.style?.forEach((s, i) => {\n const { children, ...rest } = s\n tags.push({\n tag: \"style\",\n key: `style-${i}`,\n props: toProps(rest as Record<string, string | undefined>),\n children,\n })\n })\n o.noscript?.forEach((ns, i) => {\n tags.push({ tag: \"noscript\", key: `noscript-${i}`, children: ns.children })\n })\n if (o.jsonLd) {\n tags.push({\n tag: \"script\",\n key: \"jsonld\",\n props: { type: \"application/ld+json\" },\n children: JSON.stringify(o.jsonLd),\n })\n }\n if (o.base)\n tags.push({\n tag: \"base\",\n key: \"base\",\n props: toProps(o.base as Record<string, string | undefined>),\n })\n return {\n tags,\n titleTemplate: o.titleTemplate,\n htmlAttrs: o.htmlAttrs,\n bodyAttrs: o.bodyAttrs,\n }\n}\n\n/**\n * Register head tags (title, meta, link, script, style, noscript, base, jsonLd)\n * for the current component.\n *\n * Accepts a static object or a reactive getter:\n * useHead({ title: \"My Page\", meta: [{ name: \"description\", content: \"...\" }] })\n * useHead(() => ({ title: `${count()} items` })) // updates when signal changes\n *\n * Tags are deduplicated by key — innermost component wins.\n * Requires a <HeadProvider> (CSR) or renderWithHead() (SSR) ancestor.\n */\nexport function useHead(input: UseHeadInput | (() => UseHeadInput)): void {\n const ctx = useContext(HeadContext)\n if (!ctx) return // no HeadProvider — silently no-op\n\n const id = Symbol()\n\n if (typeof input === \"function\") {\n if (typeof document !== \"undefined\") {\n // CSR: reactive — re-register whenever signals change\n effect(() => {\n ctx.add(id, buildEntry(input()))\n syncDom(ctx)\n })\n } else {\n // SSR: evaluate once synchronously (no effects on server)\n ctx.add(id, buildEntry(input()))\n }\n } else {\n ctx.add(id, buildEntry(input))\n onMount(() => {\n syncDom(ctx)\n return undefined\n })\n }\n\n onUnmount(() => {\n ctx.remove(id)\n syncDom(ctx)\n })\n}\n"],"mappings":"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"}
|
package/lib/provider.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.js","names":[],"sources":["../src/context.ts","../src/provider.ts"],"sourcesContent":["import { createContext } from \"@pyreon/core\"\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface HeadTag {\n /** HTML tag name */\n tag: \"title\" | \"meta\" | \"link\" | \"script\" | \"style\" | \"base\" | \"noscript\"\n /**\n * Deduplication key. Tags with the same key replace each other;\n * innermost component (last added) wins.\n * Example: all components setting the page title use key \"title\".\n */\n key?: string\n /** HTML attributes for the tag */\n props?: Record<string, string>\n /** Text content — for <title>, <script>, <style>, <noscript> */\n children?: string\n}\n\nexport interface UseHeadInput {\n title?: string\n /**\n * Title template — use `%s` as a placeholder for the page title.\n * Applied to the resolved title after deduplication.\n * @example useHead({ titleTemplate: \"%s | My App\" })\n */\n titleTemplate?: string | ((title: string) => string)\n meta?: Record<string, string>[]\n link?: Record<string, string>[]\n script?: ({ src?: string; children?: string } & Record<string, string | undefined>)[]\n style?: ({ children: string } & Record<string, string | undefined>)[]\n noscript?: { children: string }[]\n /** Convenience: emits a <script type=\"application/ld+json\"> tag with JSON.stringify'd content */\n jsonLd?: Record<string, unknown> | Record<string, unknown>[]\n base?: Record<string, string>\n /** Attributes to set on the <html> element (e.g. { lang: \"en\", dir: \"ltr\" }) */\n htmlAttrs?: Record<string, string>\n /** Attributes to set on the <body> element (e.g. { class: \"dark\" }) */\n bodyAttrs?: Record<string, string>\n}\n\n// ─── Context ──────────────────────────────────────────────────────────────────\n\nexport interface HeadEntry {\n tags: HeadTag[]\n titleTemplate?: string | ((title: string) => string) | undefined\n htmlAttrs?: Record<string, string> | undefined\n bodyAttrs?: Record<string, string> | undefined\n}\n\nexport interface HeadContextValue {\n add(id: symbol, entry: HeadEntry): void\n remove(id: symbol): void\n /** Returns deduplicated tags — last-added entry wins per key */\n resolve(): HeadTag[]\n /** Returns the merged titleTemplate (last-added wins) */\n resolveTitleTemplate(): (string | ((title: string) => string)) | undefined\n /** Returns merged htmlAttrs (later entries override earlier) */\n resolveHtmlAttrs(): Record<string, string>\n /** Returns merged bodyAttrs (later entries override earlier) */\n resolveBodyAttrs(): Record<string, string>\n}\n\nexport function createHeadContext(): HeadContextValue {\n const map = new Map<symbol, HeadEntry>()\n\n // ── Cached resolve ───────────────────────────────────────────────────────\n let dirty = true\n let cachedTags: HeadTag[] = []\n let cachedTitleTemplate: (string | ((title: string) => string)) | undefined\n let cachedHtmlAttrs: Record<string, string> = {}\n let cachedBodyAttrs: Record<string, string> = {}\n\n function rebuild(): void {\n if (!dirty) return\n dirty = false\n\n const keyed = new Map<string, HeadTag>()\n const unkeyed: HeadTag[] = []\n let titleTemplate: (string | ((title: string) => string)) | undefined\n const htmlAttrs: Record<string, string> = {}\n const bodyAttrs: Record<string, string> = {}\n\n for (const entry of map.values()) {\n for (const tag of entry.tags) {\n if (tag.key) keyed.set(tag.key, tag)\n else unkeyed.push(tag)\n }\n if (entry.titleTemplate !== undefined) titleTemplate = entry.titleTemplate\n if (entry.htmlAttrs) Object.assign(htmlAttrs, entry.htmlAttrs)\n if (entry.bodyAttrs) Object.assign(bodyAttrs, entry.bodyAttrs)\n }\n\n cachedTags = [...keyed.values(), ...unkeyed]\n cachedTitleTemplate = titleTemplate\n cachedHtmlAttrs = htmlAttrs\n cachedBodyAttrs = bodyAttrs\n }\n\n return {\n add(id, entry) {\n map.set(id, entry)\n dirty = true\n },\n remove(id) {\n map.delete(id)\n dirty = true\n },\n resolve() {\n rebuild()\n return cachedTags\n },\n resolveTitleTemplate() {\n rebuild()\n return cachedTitleTemplate\n },\n resolveHtmlAttrs() {\n rebuild()\n return cachedHtmlAttrs\n },\n resolveBodyAttrs() {\n rebuild()\n return cachedBodyAttrs\n },\n }\n}\n\nexport const HeadContext = createContext<HeadContextValue | null>(null)\n","import type { ComponentFn, Props, VNodeChild } from \"@pyreon/core\"\nimport { onUnmount, popContext, pushContext } from \"@pyreon/core\"\nimport type { HeadContextValue } from \"./context\"\nimport { createHeadContext, HeadContext } from \"./context\"\n\nexport interface HeadProviderProps extends Props {\n context?: HeadContextValue | undefined\n children?: VNodeChild\n}\n\n/**\n * Provides a HeadContextValue to all descendant components.\n * Wrap your app root with this to enable useHead() throughout the tree.\n *\n * If no `context` prop is passed, a new HeadContext is created automatically.\n *\n * @example\n * // Auto-create context:\n * <HeadProvider><App /></HeadProvider>\n *\n * // Explicit context (e.g. for SSR):\n * const headCtx = createHeadContext()\n * mount(h(HeadProvider, { context: headCtx }, h(App, null)), root)\n */\nexport const HeadProvider: ComponentFn<HeadProviderProps> = (props) => {\n const ctx = props.context ?? createHeadContext()\n // Push context frame synchronously (before children mount) so all descendants\n // can read HeadContext via useContext(). Pop on unmount for correct cleanup.\n const frame = new Map([[HeadContext.id, ctx]])\n pushContext(frame)\n onUnmount(() => popContext())\n\n const ch = props.children\n return (typeof ch === \"function\" ? (ch as () => VNodeChild)() : ch) as ReturnType<ComponentFn>\n}\n"],"mappings":";;;AA+DA,SAAgB,oBAAsC;CACpD,MAAM,sBAAM,IAAI,KAAwB;CAGxC,IAAI,QAAQ;CACZ,IAAI,aAAwB,EAAE;CAC9B,IAAI;CACJ,IAAI,kBAA0C,EAAE;CAChD,IAAI,kBAA0C,EAAE;CAEhD,SAAS,UAAgB;AACvB,MAAI,CAAC,MAAO;AACZ,UAAQ;EAER,MAAM,wBAAQ,IAAI,KAAsB;EACxC,MAAM,UAAqB,EAAE;EAC7B,IAAI;EACJ,MAAM,YAAoC,EAAE;EAC5C,MAAM,YAAoC,EAAE;AAE5C,OAAK,MAAM,SAAS,IAAI,QAAQ,EAAE;AAChC,QAAK,MAAM,OAAO,MAAM,KACtB,KAAI,IAAI,IAAK,OAAM,IAAI,IAAI,KAAK,IAAI;OAC/B,SAAQ,KAAK,IAAI;AAExB,OAAI,MAAM,kBAAkB,OAAW,iBAAgB,MAAM;AAC7D,OAAI,MAAM,UAAW,QAAO,OAAO,WAAW,MAAM,UAAU;AAC9D,OAAI,MAAM,UAAW,QAAO,OAAO,WAAW,MAAM,UAAU;;AAGhE,eAAa,CAAC,GAAG,MAAM,QAAQ,EAAE,GAAG,QAAQ;AAC5C,wBAAsB;AACtB,oBAAkB;AAClB,oBAAkB;;AAGpB,QAAO;EACL,IAAI,IAAI,OAAO;AACb,OAAI,IAAI,IAAI,MAAM;AAClB,WAAQ;;EAEV,OAAO,IAAI;AACT,OAAI,OAAO,GAAG;AACd,WAAQ;;EAEV,UAAU;AACR,YAAS;AACT,UAAO;;EAET,uBAAuB;AACrB,YAAS;AACT,UAAO;;EAET,mBAAmB;AACjB,YAAS;AACT,UAAO;;EAET,mBAAmB;AACjB,YAAS;AACT,UAAO;;EAEV;;AAGH,MAAa,cAAc,cAAuC,KAAK;;;;;;;;;;;;;;;;;;ACvGvE,MAAa,gBAAgD,UAAU;CACrE,MAAM,MAAM,MAAM,WAAW,mBAAmB;AAIhD,aADc,IAAI,IAAI,CAAC,CAAC,YAAY,IAAI,IAAI,CAAC,CAAC,CAC5B;AAClB,iBAAgB,YAAY,CAAC;CAE7B,MAAM,KAAK,MAAM;AACjB,QAAQ,OAAO,OAAO,aAAc,IAAyB,GAAG"}
|
|
1
|
+
{"version":3,"file":"provider.js","names":[],"sources":["../src/context.ts","../src/provider.ts"],"sourcesContent":["import { createContext } from \"@pyreon/core\"\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface HeadTag {\n /** HTML tag name */\n tag: \"title\" | \"meta\" | \"link\" | \"script\" | \"style\" | \"base\" | \"noscript\"\n /**\n * Deduplication key. Tags with the same key replace each other;\n * innermost component (last added) wins.\n * Example: all components setting the page title use key \"title\".\n */\n key?: string\n /** HTML attributes for the tag */\n props?: Record<string, string>\n /** Text content — for <title>, <script>, <style>, <noscript> */\n children?: string\n}\n\n// ─── Strict tag types ────────────────────────────────────────────────────────\n\n/** Standard `<meta>` tag attributes. Catches typos like `{ naem: \"description\" }`. */\nexport interface MetaTag {\n /** Standard meta name (e.g. \"description\", \"viewport\", \"robots\") */\n name?: string\n /** Open Graph / social property (e.g. \"og:title\", \"twitter:card\") */\n property?: string\n /** HTTP equivalent header (e.g. \"refresh\", \"content-type\") */\n \"http-equiv\"?: string\n /** Value associated with name, property, or http-equiv */\n content?: string\n /** Document character encoding (e.g. \"utf-8\") */\n charset?: string\n /** Schema.org itemprop */\n itemprop?: string\n /** Media condition for applicability (e.g. \"(prefers-color-scheme: dark)\") */\n media?: string\n}\n\n/** Standard `<link>` tag attributes. */\nexport interface LinkTag {\n /** Relationship to the current document (e.g. \"stylesheet\", \"icon\", \"canonical\") */\n rel?: string\n /** URL of the linked resource */\n href?: string\n /** Resource type hint for preloading (e.g. \"style\", \"script\", \"font\") */\n as?: string\n /** MIME type (e.g. \"text/css\", \"image/png\") */\n type?: string\n /** Media query for conditional loading */\n media?: string\n /** CORS mode */\n crossorigin?: string\n /** Subresource integrity hash */\n integrity?: string\n /** Icon sizes (e.g. \"32x32\", \"any\") */\n sizes?: string\n /** Language of the linked resource */\n hreflang?: string\n /** Title for the link (used for alternate stylesheets) */\n title?: string\n /** Fetch priority hint */\n fetchpriority?: \"high\" | \"low\" | \"auto\"\n /** Referrer policy */\n referrerpolicy?: string\n /** Image source set for preloading responsive images */\n imagesrcset?: string\n /** Image sizes for preloading responsive images */\n imagesizes?: string\n /** Disable the resource (for stylesheets) */\n disabled?: string\n /** Color for mask-icon */\n color?: string\n}\n\n/** Standard `<script>` tag attributes. */\nexport interface ScriptTag {\n /** External script URL */\n src?: string\n /** Script MIME type or module type (e.g. \"module\", \"importmap\") */\n type?: string\n /** Load asynchronously */\n async?: string\n /** Defer execution until document is parsed */\n defer?: string\n /** CORS mode */\n crossorigin?: string\n /** Subresource integrity hash */\n integrity?: string\n /** Exclude from module-supporting browsers */\n nomodule?: string\n /** Referrer policy */\n referrerpolicy?: string\n /** Fetch priority hint */\n fetchpriority?: string\n /** Inline script content */\n children?: string\n}\n\n/** Standard `<style>` tag attributes. */\nexport interface StyleTag {\n /** Inline CSS content (required) */\n children: string\n /** Media query for conditional styles */\n media?: string\n /** Nonce for CSP */\n nonce?: string\n /** Title for alternate stylesheets */\n title?: string\n /** Render-blocking behavior */\n blocking?: string\n}\n\n/** Standard `<base>` tag attributes. */\nexport interface BaseTag {\n /** Base URL for relative URLs in the document */\n href?: string\n /** Default target for links and forms */\n target?: \"_blank\" | \"_self\" | \"_parent\" | \"_top\"\n}\n\nexport interface UseHeadInput {\n title?: string\n /**\n * Title template — use `%s` as a placeholder for the page title.\n * Applied to the resolved title after deduplication.\n * @example useHead({ titleTemplate: \"%s | My App\" })\n */\n titleTemplate?: string | ((title: string) => string)\n meta?: MetaTag[]\n link?: LinkTag[]\n script?: ScriptTag[]\n style?: StyleTag[]\n noscript?: { children: string }[]\n /** Convenience: emits a <script type=\"application/ld+json\"> tag with JSON.stringify'd content */\n jsonLd?: Record<string, unknown> | Record<string, unknown>[]\n base?: BaseTag\n /** Attributes to set on the <html> element (e.g. { lang: \"en\", dir: \"ltr\" }) */\n htmlAttrs?: Record<string, string>\n /** Attributes to set on the <body> element (e.g. { class: \"dark\" }) */\n bodyAttrs?: Record<string, string>\n}\n\n// ─── Context ──────────────────────────────────────────────────────────────────\n\nexport interface HeadEntry {\n tags: HeadTag[]\n titleTemplate?: string | ((title: string) => string) | undefined\n htmlAttrs?: Record<string, string> | undefined\n bodyAttrs?: Record<string, string> | undefined\n}\n\nexport interface HeadContextValue {\n add(id: symbol, entry: HeadEntry): void\n remove(id: symbol): void\n /** Returns deduplicated tags — last-added entry wins per key */\n resolve(): HeadTag[]\n /** Returns the merged titleTemplate (last-added wins) */\n resolveTitleTemplate(): (string | ((title: string) => string)) | undefined\n /** Returns merged htmlAttrs (later entries override earlier) */\n resolveHtmlAttrs(): Record<string, string>\n /** Returns merged bodyAttrs (later entries override earlier) */\n resolveBodyAttrs(): Record<string, string>\n}\n\nexport function createHeadContext(): HeadContextValue {\n const map = new Map<symbol, HeadEntry>()\n\n // ── Cached resolve ───────────────────────────────────────────────────────\n let dirty = true\n let cachedTags: HeadTag[] = []\n let cachedTitleTemplate: (string | ((title: string) => string)) | undefined\n let cachedHtmlAttrs: Record<string, string> = {}\n let cachedBodyAttrs: Record<string, string> = {}\n\n function rebuild(): void {\n if (!dirty) return\n dirty = false\n\n const keyed = new Map<string, HeadTag>()\n const unkeyed: HeadTag[] = []\n let titleTemplate: (string | ((title: string) => string)) | undefined\n const htmlAttrs: Record<string, string> = {}\n const bodyAttrs: Record<string, string> = {}\n\n for (const entry of map.values()) {\n for (const tag of entry.tags) {\n if (tag.key) keyed.set(tag.key, tag)\n else unkeyed.push(tag)\n }\n if (entry.titleTemplate !== undefined) titleTemplate = entry.titleTemplate\n if (entry.htmlAttrs) Object.assign(htmlAttrs, entry.htmlAttrs)\n if (entry.bodyAttrs) Object.assign(bodyAttrs, entry.bodyAttrs)\n }\n\n cachedTags = [...keyed.values(), ...unkeyed]\n cachedTitleTemplate = titleTemplate\n cachedHtmlAttrs = htmlAttrs\n cachedBodyAttrs = bodyAttrs\n }\n\n return {\n add(id, entry) {\n map.set(id, entry)\n dirty = true\n },\n remove(id) {\n map.delete(id)\n dirty = true\n },\n resolve() {\n rebuild()\n return cachedTags\n },\n resolveTitleTemplate() {\n rebuild()\n return cachedTitleTemplate\n },\n resolveHtmlAttrs() {\n rebuild()\n return cachedHtmlAttrs\n },\n resolveBodyAttrs() {\n rebuild()\n return cachedBodyAttrs\n },\n }\n}\n\nexport const HeadContext = createContext<HeadContextValue | null>(null)\n","import type { ComponentFn, Props, VNodeChild } from \"@pyreon/core\"\nimport { onUnmount, popContext, pushContext } from \"@pyreon/core\"\nimport type { HeadContextValue } from \"./context\"\nimport { createHeadContext, HeadContext } from \"./context\"\n\nexport interface HeadProviderProps extends Props {\n context?: HeadContextValue | undefined\n children?: VNodeChild\n}\n\n/**\n * Provides a HeadContextValue to all descendant components.\n * Wrap your app root with this to enable useHead() throughout the tree.\n *\n * If no `context` prop is passed, a new HeadContext is created automatically.\n *\n * @example\n * // Auto-create context:\n * <HeadProvider><App /></HeadProvider>\n *\n * // Explicit context (e.g. for SSR):\n * const headCtx = createHeadContext()\n * mount(h(HeadProvider, { context: headCtx }, h(App, null)), root)\n */\nexport const HeadProvider: ComponentFn<HeadProviderProps> = (props) => {\n const ctx = props.context ?? createHeadContext()\n // Push context frame synchronously (before children mount) so all descendants\n // can read HeadContext via useContext(). Pop on unmount for correct cleanup.\n const frame = new Map([[HeadContext.id, ctx]])\n pushContext(frame)\n onUnmount(() => popContext())\n\n const ch = props.children\n return (typeof ch === \"function\" ? (ch as () => VNodeChild)() : ch) as ReturnType<ComponentFn>\n}\n"],"mappings":";;;AAqKA,SAAgB,oBAAsC;CACpD,MAAM,sBAAM,IAAI,KAAwB;CAGxC,IAAI,QAAQ;CACZ,IAAI,aAAwB,EAAE;CAC9B,IAAI;CACJ,IAAI,kBAA0C,EAAE;CAChD,IAAI,kBAA0C,EAAE;CAEhD,SAAS,UAAgB;AACvB,MAAI,CAAC,MAAO;AACZ,UAAQ;EAER,MAAM,wBAAQ,IAAI,KAAsB;EACxC,MAAM,UAAqB,EAAE;EAC7B,IAAI;EACJ,MAAM,YAAoC,EAAE;EAC5C,MAAM,YAAoC,EAAE;AAE5C,OAAK,MAAM,SAAS,IAAI,QAAQ,EAAE;AAChC,QAAK,MAAM,OAAO,MAAM,KACtB,KAAI,IAAI,IAAK,OAAM,IAAI,IAAI,KAAK,IAAI;OAC/B,SAAQ,KAAK,IAAI;AAExB,OAAI,MAAM,kBAAkB,OAAW,iBAAgB,MAAM;AAC7D,OAAI,MAAM,UAAW,QAAO,OAAO,WAAW,MAAM,UAAU;AAC9D,OAAI,MAAM,UAAW,QAAO,OAAO,WAAW,MAAM,UAAU;;AAGhE,eAAa,CAAC,GAAG,MAAM,QAAQ,EAAE,GAAG,QAAQ;AAC5C,wBAAsB;AACtB,oBAAkB;AAClB,oBAAkB;;AAGpB,QAAO;EACL,IAAI,IAAI,OAAO;AACb,OAAI,IAAI,IAAI,MAAM;AAClB,WAAQ;;EAEV,OAAO,IAAI;AACT,OAAI,OAAO,GAAG;AACd,WAAQ;;EAEV,UAAU;AACR,YAAS;AACT,UAAO;;EAET,uBAAuB;AACrB,YAAS;AACT,UAAO;;EAET,mBAAmB;AACjB,YAAS;AACT,UAAO;;EAET,mBAAmB;AACjB,YAAS;AACT,UAAO;;EAEV;;AAGH,MAAa,cAAc,cAAuC,KAAK;;;;;;;;;;;;;;;;;;AC7MvE,MAAa,gBAAgD,UAAU;CACrE,MAAM,MAAM,MAAM,WAAW,mBAAmB;AAIhD,aADc,IAAI,IAAI,CAAC,CAAC,YAAY,IAAI,IAAI,CAAC,CAAC,CAC5B;AAClB,iBAAgB,YAAY,CAAC;CAE7B,MAAM,KAAK,MAAM;AACjB,QAAQ,OAAO,OAAO,aAAc,IAAyB,GAAG"}
|
package/lib/ssr.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr.js","names":[],"sources":["../src/context.ts","../src/ssr.ts"],"sourcesContent":["import { createContext } from \"@pyreon/core\"\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface HeadTag {\n /** HTML tag name */\n tag: \"title\" | \"meta\" | \"link\" | \"script\" | \"style\" | \"base\" | \"noscript\"\n /**\n * Deduplication key. Tags with the same key replace each other;\n * innermost component (last added) wins.\n * Example: all components setting the page title use key \"title\".\n */\n key?: string\n /** HTML attributes for the tag */\n props?: Record<string, string>\n /** Text content — for <title>, <script>, <style>, <noscript> */\n children?: string\n}\n\nexport interface UseHeadInput {\n title?: string\n /**\n * Title template — use `%s` as a placeholder for the page title.\n * Applied to the resolved title after deduplication.\n * @example useHead({ titleTemplate: \"%s | My App\" })\n */\n titleTemplate?: string | ((title: string) => string)\n meta?: Record<string, string>[]\n link?: Record<string, string>[]\n script?: ({ src?: string; children?: string } & Record<string, string | undefined>)[]\n style?: ({ children: string } & Record<string, string | undefined>)[]\n noscript?: { children: string }[]\n /** Convenience: emits a <script type=\"application/ld+json\"> tag with JSON.stringify'd content */\n jsonLd?: Record<string, unknown> | Record<string, unknown>[]\n base?: Record<string, string>\n /** Attributes to set on the <html> element (e.g. { lang: \"en\", dir: \"ltr\" }) */\n htmlAttrs?: Record<string, string>\n /** Attributes to set on the <body> element (e.g. { class: \"dark\" }) */\n bodyAttrs?: Record<string, string>\n}\n\n// ─── Context ──────────────────────────────────────────────────────────────────\n\nexport interface HeadEntry {\n tags: HeadTag[]\n titleTemplate?: string | ((title: string) => string) | undefined\n htmlAttrs?: Record<string, string> | undefined\n bodyAttrs?: Record<string, string> | undefined\n}\n\nexport interface HeadContextValue {\n add(id: symbol, entry: HeadEntry): void\n remove(id: symbol): void\n /** Returns deduplicated tags — last-added entry wins per key */\n resolve(): HeadTag[]\n /** Returns the merged titleTemplate (last-added wins) */\n resolveTitleTemplate(): (string | ((title: string) => string)) | undefined\n /** Returns merged htmlAttrs (later entries override earlier) */\n resolveHtmlAttrs(): Record<string, string>\n /** Returns merged bodyAttrs (later entries override earlier) */\n resolveBodyAttrs(): Record<string, string>\n}\n\nexport function createHeadContext(): HeadContextValue {\n const map = new Map<symbol, HeadEntry>()\n\n // ── Cached resolve ───────────────────────────────────────────────────────\n let dirty = true\n let cachedTags: HeadTag[] = []\n let cachedTitleTemplate: (string | ((title: string) => string)) | undefined\n let cachedHtmlAttrs: Record<string, string> = {}\n let cachedBodyAttrs: Record<string, string> = {}\n\n function rebuild(): void {\n if (!dirty) return\n dirty = false\n\n const keyed = new Map<string, HeadTag>()\n const unkeyed: HeadTag[] = []\n let titleTemplate: (string | ((title: string) => string)) | undefined\n const htmlAttrs: Record<string, string> = {}\n const bodyAttrs: Record<string, string> = {}\n\n for (const entry of map.values()) {\n for (const tag of entry.tags) {\n if (tag.key) keyed.set(tag.key, tag)\n else unkeyed.push(tag)\n }\n if (entry.titleTemplate !== undefined) titleTemplate = entry.titleTemplate\n if (entry.htmlAttrs) Object.assign(htmlAttrs, entry.htmlAttrs)\n if (entry.bodyAttrs) Object.assign(bodyAttrs, entry.bodyAttrs)\n }\n\n cachedTags = [...keyed.values(), ...unkeyed]\n cachedTitleTemplate = titleTemplate\n cachedHtmlAttrs = htmlAttrs\n cachedBodyAttrs = bodyAttrs\n }\n\n return {\n add(id, entry) {\n map.set(id, entry)\n dirty = true\n },\n remove(id) {\n map.delete(id)\n dirty = true\n },\n resolve() {\n rebuild()\n return cachedTags\n },\n resolveTitleTemplate() {\n rebuild()\n return cachedTitleTemplate\n },\n resolveHtmlAttrs() {\n rebuild()\n return cachedHtmlAttrs\n },\n resolveBodyAttrs() {\n rebuild()\n return cachedBodyAttrs\n },\n }\n}\n\nexport const HeadContext = createContext<HeadContextValue | null>(null)\n","import type { ComponentFn, VNode } from \"@pyreon/core\"\nimport { h, pushContext } from \"@pyreon/core\"\nimport { renderToString } from \"@pyreon/runtime-server\"\nimport type { HeadTag } from \"./context\"\nimport { createHeadContext, HeadContext } from \"./context\"\n\nconst VOID_TAGS = new Set([\"meta\", \"link\", \"base\"])\n\n/**\n * Render a Pyreon app to an HTML fragment + a serialized <head> string.\n *\n * The returned `head` string can be injected directly into your HTML template:\n *\n * @example\n * const { html, head } = await renderWithHead(h(App, null))\n * const page = `<!DOCTYPE html>\n * <html>\n * <head>\n * <meta charset=\"UTF-8\" />\n * ${head}\n * </head>\n * <body><div id=\"app\">${html}</div></body>\n * </html>`\n */\nexport interface RenderWithHeadResult {\n html: string\n head: string\n /** Attributes to set on the <html> element */\n htmlAttrs: Record<string, string>\n /** Attributes to set on the <body> element */\n bodyAttrs: Record<string, string>\n}\n\nexport async function renderWithHead(app: VNode): Promise<RenderWithHeadResult> {\n const ctx = createHeadContext()\n\n // HeadInjector runs inside renderToString's ALS scope, so pushContext reaches\n // the per-request context stack rather than the module-level fallback stack.\n function HeadInjector(): VNode {\n pushContext(new Map([[HeadContext.id, ctx]]))\n return app\n }\n\n const html = await renderToString(h(HeadInjector as ComponentFn, null))\n const titleTemplate = ctx.resolveTitleTemplate()\n const head = ctx\n .resolve()\n .map((tag) => serializeTag(tag, titleTemplate))\n .join(\"\\n \")\n return {\n html,\n head,\n htmlAttrs: ctx.resolveHtmlAttrs(),\n bodyAttrs: ctx.resolveBodyAttrs(),\n }\n}\n\nfunction serializeTag(tag: HeadTag, titleTemplate?: string | ((title: string) => string)): string {\n if (tag.tag === \"title\") {\n const raw = tag.children || \"\"\n const title = titleTemplate\n ? typeof titleTemplate === \"function\"\n ? titleTemplate(raw)\n : titleTemplate.replace(/%s/g, raw)\n : raw\n return `<title>${esc(title)}</title>`\n }\n const props = tag.props as Record<string, string> | undefined\n const attrs = props\n ? Object.entries(props)\n .map(([k, v]) => `${k}=\"${esc(v)}\"`)\n .join(\" \")\n : \"\"\n const open = attrs ? `<${tag.tag} ${attrs}` : `<${tag.tag}`\n if (VOID_TAGS.has(tag.tag)) return `${open} />`\n const content = tag.children || \"\"\n // Escape sequences that could break out of script/style/noscript blocks:\n // 1. Closing tags like </script> — use Unicode escape in the slash\n // 2. HTML comment openers <!-- that could confuse parsers\n const body = content.replace(/<\\/(script|style|noscript)/gi, \"<\\\\/$1\").replace(/<!--/g, \"<\\\\!--\")\n return `${open}>${body}</${tag.tag}>`\n}\n\nconst ESC_RE = /[&<>\"]/g\nconst ESC_MAP: Record<string, string> = { \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\" }\n\nfunction esc(s: string): string {\n return ESC_RE.test(s) ? s.replace(ESC_RE, (ch) => ESC_MAP[ch] as string) : s\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ssr.js","names":[],"sources":["../src/context.ts","../src/ssr.ts"],"sourcesContent":["import { createContext } from \"@pyreon/core\"\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface HeadTag {\n /** HTML tag name */\n tag: \"title\" | \"meta\" | \"link\" | \"script\" | \"style\" | \"base\" | \"noscript\"\n /**\n * Deduplication key. Tags with the same key replace each other;\n * innermost component (last added) wins.\n * Example: all components setting the page title use key \"title\".\n */\n key?: string\n /** HTML attributes for the tag */\n props?: Record<string, string>\n /** Text content — for <title>, <script>, <style>, <noscript> */\n children?: string\n}\n\n// ─── Strict tag types ────────────────────────────────────────────────────────\n\n/** Standard `<meta>` tag attributes. Catches typos like `{ naem: \"description\" }`. */\nexport interface MetaTag {\n /** Standard meta name (e.g. \"description\", \"viewport\", \"robots\") */\n name?: string\n /** Open Graph / social property (e.g. \"og:title\", \"twitter:card\") */\n property?: string\n /** HTTP equivalent header (e.g. \"refresh\", \"content-type\") */\n \"http-equiv\"?: string\n /** Value associated with name, property, or http-equiv */\n content?: string\n /** Document character encoding (e.g. \"utf-8\") */\n charset?: string\n /** Schema.org itemprop */\n itemprop?: string\n /** Media condition for applicability (e.g. \"(prefers-color-scheme: dark)\") */\n media?: string\n}\n\n/** Standard `<link>` tag attributes. */\nexport interface LinkTag {\n /** Relationship to the current document (e.g. \"stylesheet\", \"icon\", \"canonical\") */\n rel?: string\n /** URL of the linked resource */\n href?: string\n /** Resource type hint for preloading (e.g. \"style\", \"script\", \"font\") */\n as?: string\n /** MIME type (e.g. \"text/css\", \"image/png\") */\n type?: string\n /** Media query for conditional loading */\n media?: string\n /** CORS mode */\n crossorigin?: string\n /** Subresource integrity hash */\n integrity?: string\n /** Icon sizes (e.g. \"32x32\", \"any\") */\n sizes?: string\n /** Language of the linked resource */\n hreflang?: string\n /** Title for the link (used for alternate stylesheets) */\n title?: string\n /** Fetch priority hint */\n fetchpriority?: \"high\" | \"low\" | \"auto\"\n /** Referrer policy */\n referrerpolicy?: string\n /** Image source set for preloading responsive images */\n imagesrcset?: string\n /** Image sizes for preloading responsive images */\n imagesizes?: string\n /** Disable the resource (for stylesheets) */\n disabled?: string\n /** Color for mask-icon */\n color?: string\n}\n\n/** Standard `<script>` tag attributes. */\nexport interface ScriptTag {\n /** External script URL */\n src?: string\n /** Script MIME type or module type (e.g. \"module\", \"importmap\") */\n type?: string\n /** Load asynchronously */\n async?: string\n /** Defer execution until document is parsed */\n defer?: string\n /** CORS mode */\n crossorigin?: string\n /** Subresource integrity hash */\n integrity?: string\n /** Exclude from module-supporting browsers */\n nomodule?: string\n /** Referrer policy */\n referrerpolicy?: string\n /** Fetch priority hint */\n fetchpriority?: string\n /** Inline script content */\n children?: string\n}\n\n/** Standard `<style>` tag attributes. */\nexport interface StyleTag {\n /** Inline CSS content (required) */\n children: string\n /** Media query for conditional styles */\n media?: string\n /** Nonce for CSP */\n nonce?: string\n /** Title for alternate stylesheets */\n title?: string\n /** Render-blocking behavior */\n blocking?: string\n}\n\n/** Standard `<base>` tag attributes. */\nexport interface BaseTag {\n /** Base URL for relative URLs in the document */\n href?: string\n /** Default target for links and forms */\n target?: \"_blank\" | \"_self\" | \"_parent\" | \"_top\"\n}\n\nexport interface UseHeadInput {\n title?: string\n /**\n * Title template — use `%s` as a placeholder for the page title.\n * Applied to the resolved title after deduplication.\n * @example useHead({ titleTemplate: \"%s | My App\" })\n */\n titleTemplate?: string | ((title: string) => string)\n meta?: MetaTag[]\n link?: LinkTag[]\n script?: ScriptTag[]\n style?: StyleTag[]\n noscript?: { children: string }[]\n /** Convenience: emits a <script type=\"application/ld+json\"> tag with JSON.stringify'd content */\n jsonLd?: Record<string, unknown> | Record<string, unknown>[]\n base?: BaseTag\n /** Attributes to set on the <html> element (e.g. { lang: \"en\", dir: \"ltr\" }) */\n htmlAttrs?: Record<string, string>\n /** Attributes to set on the <body> element (e.g. { class: \"dark\" }) */\n bodyAttrs?: Record<string, string>\n}\n\n// ─── Context ──────────────────────────────────────────────────────────────────\n\nexport interface HeadEntry {\n tags: HeadTag[]\n titleTemplate?: string | ((title: string) => string) | undefined\n htmlAttrs?: Record<string, string> | undefined\n bodyAttrs?: Record<string, string> | undefined\n}\n\nexport interface HeadContextValue {\n add(id: symbol, entry: HeadEntry): void\n remove(id: symbol): void\n /** Returns deduplicated tags — last-added entry wins per key */\n resolve(): HeadTag[]\n /** Returns the merged titleTemplate (last-added wins) */\n resolveTitleTemplate(): (string | ((title: string) => string)) | undefined\n /** Returns merged htmlAttrs (later entries override earlier) */\n resolveHtmlAttrs(): Record<string, string>\n /** Returns merged bodyAttrs (later entries override earlier) */\n resolveBodyAttrs(): Record<string, string>\n}\n\nexport function createHeadContext(): HeadContextValue {\n const map = new Map<symbol, HeadEntry>()\n\n // ── Cached resolve ───────────────────────────────────────────────────────\n let dirty = true\n let cachedTags: HeadTag[] = []\n let cachedTitleTemplate: (string | ((title: string) => string)) | undefined\n let cachedHtmlAttrs: Record<string, string> = {}\n let cachedBodyAttrs: Record<string, string> = {}\n\n function rebuild(): void {\n if (!dirty) return\n dirty = false\n\n const keyed = new Map<string, HeadTag>()\n const unkeyed: HeadTag[] = []\n let titleTemplate: (string | ((title: string) => string)) | undefined\n const htmlAttrs: Record<string, string> = {}\n const bodyAttrs: Record<string, string> = {}\n\n for (const entry of map.values()) {\n for (const tag of entry.tags) {\n if (tag.key) keyed.set(tag.key, tag)\n else unkeyed.push(tag)\n }\n if (entry.titleTemplate !== undefined) titleTemplate = entry.titleTemplate\n if (entry.htmlAttrs) Object.assign(htmlAttrs, entry.htmlAttrs)\n if (entry.bodyAttrs) Object.assign(bodyAttrs, entry.bodyAttrs)\n }\n\n cachedTags = [...keyed.values(), ...unkeyed]\n cachedTitleTemplate = titleTemplate\n cachedHtmlAttrs = htmlAttrs\n cachedBodyAttrs = bodyAttrs\n }\n\n return {\n add(id, entry) {\n map.set(id, entry)\n dirty = true\n },\n remove(id) {\n map.delete(id)\n dirty = true\n },\n resolve() {\n rebuild()\n return cachedTags\n },\n resolveTitleTemplate() {\n rebuild()\n return cachedTitleTemplate\n },\n resolveHtmlAttrs() {\n rebuild()\n return cachedHtmlAttrs\n },\n resolveBodyAttrs() {\n rebuild()\n return cachedBodyAttrs\n },\n }\n}\n\nexport const HeadContext = createContext<HeadContextValue | null>(null)\n","import type { ComponentFn, VNode } from \"@pyreon/core\"\nimport { h, pushContext } from \"@pyreon/core\"\nimport { renderToString } from \"@pyreon/runtime-server\"\nimport type { HeadTag } from \"./context\"\nimport { createHeadContext, HeadContext } from \"./context\"\n\nconst VOID_TAGS = new Set([\"meta\", \"link\", \"base\"])\n\n/**\n * Render a Pyreon app to an HTML fragment + a serialized <head> string.\n *\n * The returned `head` string can be injected directly into your HTML template:\n *\n * @example\n * const { html, head } = await renderWithHead(h(App, null))\n * const page = `<!DOCTYPE html>\n * <html>\n * <head>\n * <meta charset=\"UTF-8\" />\n * ${head}\n * </head>\n * <body><div id=\"app\">${html}</div></body>\n * </html>`\n */\nexport interface RenderWithHeadResult {\n html: string\n head: string\n /** Attributes to set on the <html> element */\n htmlAttrs: Record<string, string>\n /** Attributes to set on the <body> element */\n bodyAttrs: Record<string, string>\n}\n\nexport async function renderWithHead(app: VNode): Promise<RenderWithHeadResult> {\n const ctx = createHeadContext()\n\n // HeadInjector runs inside renderToString's ALS scope, so pushContext reaches\n // the per-request context stack rather than the module-level fallback stack.\n function HeadInjector(): VNode {\n pushContext(new Map([[HeadContext.id, ctx]]))\n return app\n }\n\n const html = await renderToString(h(HeadInjector as ComponentFn, null))\n const titleTemplate = ctx.resolveTitleTemplate()\n const head = ctx\n .resolve()\n .map((tag) => serializeTag(tag, titleTemplate))\n .join(\"\\n \")\n return {\n html,\n head,\n htmlAttrs: ctx.resolveHtmlAttrs(),\n bodyAttrs: ctx.resolveBodyAttrs(),\n }\n}\n\nfunction serializeTag(tag: HeadTag, titleTemplate?: string | ((title: string) => string)): string {\n if (tag.tag === \"title\") {\n const raw = tag.children || \"\"\n const title = titleTemplate\n ? typeof titleTemplate === \"function\"\n ? titleTemplate(raw)\n : titleTemplate.replace(/%s/g, raw)\n : raw\n return `<title>${esc(title)}</title>`\n }\n const props = tag.props as Record<string, string> | undefined\n const attrs = props\n ? Object.entries(props)\n .map(([k, v]) => `${k}=\"${esc(v)}\"`)\n .join(\" \")\n : \"\"\n const open = attrs ? `<${tag.tag} ${attrs}` : `<${tag.tag}`\n if (VOID_TAGS.has(tag.tag)) return `${open} />`\n const content = tag.children || \"\"\n // Escape sequences that could break out of script/style/noscript blocks:\n // 1. Closing tags like </script> — use Unicode escape in the slash\n // 2. HTML comment openers <!-- that could confuse parsers\n const body = content.replace(/<\\/(script|style|noscript)/gi, \"<\\\\/$1\").replace(/<!--/g, \"<\\\\!--\")\n return `${open}>${body}</${tag.tag}>`\n}\n\nconst ESC_RE = /[&<>\"]/g\nconst ESC_MAP: Record<string, string> = { \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\" }\n\nfunction esc(s: string): string {\n return ESC_RE.test(s) ? s.replace(ESC_RE, (ch) => ESC_MAP[ch] as string) : s\n}\n"],"mappings":"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"}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -181,6 +181,12 @@ function syncElementAttrs(el, attrs) {
|
|
|
181
181
|
|
|
182
182
|
//#endregion
|
|
183
183
|
//#region src/use-head.ts
|
|
184
|
+
/** Cast a strict tag interface to the internal props format, stripping undefined values */
|
|
185
|
+
function toProps(obj) {
|
|
186
|
+
const result = {};
|
|
187
|
+
for (const [k, v] of Object.entries(obj)) if (v !== void 0) result[k] = v;
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
184
190
|
function buildEntry(o) {
|
|
185
191
|
const tags = [];
|
|
186
192
|
if (o.title != null) tags.push({
|
|
@@ -192,14 +198,14 @@ function buildEntry(o) {
|
|
|
192
198
|
tags.push({
|
|
193
199
|
tag: "meta",
|
|
194
200
|
key: m.name ?? m.property ?? `meta-${i}`,
|
|
195
|
-
props: m
|
|
201
|
+
props: toProps(m)
|
|
196
202
|
});
|
|
197
203
|
});
|
|
198
204
|
o.link?.forEach((l, i) => {
|
|
199
205
|
tags.push({
|
|
200
206
|
tag: "link",
|
|
201
207
|
key: l.href ? `link-${l.rel || ""}-${l.href}` : l.rel ? `link-${l.rel}` : `link-${i}`,
|
|
202
|
-
props: l
|
|
208
|
+
props: toProps(l)
|
|
203
209
|
});
|
|
204
210
|
});
|
|
205
211
|
o.script?.forEach((s, i) => {
|
|
@@ -210,7 +216,7 @@ function buildEntry(o) {
|
|
|
210
216
|
tags.push({
|
|
211
217
|
tag: "script",
|
|
212
218
|
key: s.src ?? `script-${i}`,
|
|
213
|
-
props: rest,
|
|
219
|
+
props: toProps(rest),
|
|
214
220
|
...(children != null ? {
|
|
215
221
|
children
|
|
216
222
|
} : {})
|
|
@@ -224,7 +230,7 @@ function buildEntry(o) {
|
|
|
224
230
|
tags.push({
|
|
225
231
|
tag: "style",
|
|
226
232
|
key: `style-${i}`,
|
|
227
|
-
props: rest,
|
|
233
|
+
props: toProps(rest),
|
|
228
234
|
children
|
|
229
235
|
});
|
|
230
236
|
});
|
|
@@ -246,7 +252,7 @@ function buildEntry(o) {
|
|
|
246
252
|
if (o.base) tags.push({
|
|
247
253
|
tag: "base",
|
|
248
254
|
key: "base",
|
|
249
|
-
props: o.base
|
|
255
|
+
props: toProps(o.base)
|
|
250
256
|
});
|
|
251
257
|
return {
|
|
252
258
|
tags,
|
package/lib/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/context.ts","../../../src/provider.ts","../../../src/ssr.ts","../../../src/dom.ts","../../../src/use-head.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/context.ts","../../../src/provider.ts","../../../src/ssr.ts","../../../src/dom.ts","../../../src/use-head.ts"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider2.d.ts","names":[],"sources":["../../../src/context.ts","../../../src/provider.ts"],"mappings":";;;UAIiB,OAAA;;EAEf,GAAA;EAFe;;;;;EAQf,GAAA;EAEA;EAAA,KAAA,GAAQ,MAAA;EAER;EAAA,QAAA;AAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"provider2.d.ts","names":[],"sources":["../../../src/context.ts","../../../src/provider.ts"],"mappings":";;;UAIiB,OAAA;;EAEf,GAAA;EAFe;;;;;EAQf,GAAA;EAEA;EAAA,KAAA,GAAQ,MAAA;EAER;EAAA,QAAA;AAAA;AAAA,UAiIe,SAAA;EACf,IAAA,EAAM,OAAA;EACN,aAAA,cAA2B,KAAA;EAC3B,SAAA,GAAY,MAAA;EACZ,SAAA,GAAY,MAAA;AAAA;AAAA,UAGG,gBAAA;EACf,GAAA,CAAI,EAAA,UAAY,KAAA,EAAO,SAAA;EACvB,MAAA,CAAO,EAAA;;EAEP,OAAA,IAAW,OAAA;;EAEX,oBAAA,gBAAoC,KAAA;;EAEpC,gBAAA,IAAoB,MAAA;;EAEpB,gBAAA,IAAoB,MAAA;AAAA;;;UC7JL,iBAAA,SAA0B,KAAA;EACzC,OAAA,GAAU,gBAAA;EACV,QAAA,GAAW,UAAA;AAAA;;;;;;;;;AD0Ib;;;;;;cCzHa,YAAA,EAAc,WAAA,CAAY,iBAAA"}
|
package/lib/types/ssr.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr.d.ts","names":[],"sources":["../../../src/context.ts","../../../src/ssr.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"ssr.d.ts","names":[],"sources":["../../../src/context.ts","../../../src/ssr.ts"],"mappings":"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"}
|
package/lib/types/use-head.d.ts
CHANGED
|
@@ -95,6 +95,12 @@ function syncElementAttrs(el, attrs) {
|
|
|
95
95
|
|
|
96
96
|
//#endregion
|
|
97
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
|
+
}
|
|
98
104
|
function buildEntry(o) {
|
|
99
105
|
const tags = [];
|
|
100
106
|
if (o.title != null) tags.push({
|
|
@@ -106,14 +112,14 @@ function buildEntry(o) {
|
|
|
106
112
|
tags.push({
|
|
107
113
|
tag: "meta",
|
|
108
114
|
key: m.name ?? m.property ?? `meta-${i}`,
|
|
109
|
-
props: m
|
|
115
|
+
props: toProps(m)
|
|
110
116
|
});
|
|
111
117
|
});
|
|
112
118
|
o.link?.forEach((l, i) => {
|
|
113
119
|
tags.push({
|
|
114
120
|
tag: "link",
|
|
115
121
|
key: l.href ? `link-${l.rel || ""}-${l.href}` : l.rel ? `link-${l.rel}` : `link-${i}`,
|
|
116
|
-
props: l
|
|
122
|
+
props: toProps(l)
|
|
117
123
|
});
|
|
118
124
|
});
|
|
119
125
|
o.script?.forEach((s, i) => {
|
|
@@ -124,7 +130,7 @@ function buildEntry(o) {
|
|
|
124
130
|
tags.push({
|
|
125
131
|
tag: "script",
|
|
126
132
|
key: s.src ?? `script-${i}`,
|
|
127
|
-
props: rest,
|
|
133
|
+
props: toProps(rest),
|
|
128
134
|
...(children != null ? {
|
|
129
135
|
children
|
|
130
136
|
} : {})
|
|
@@ -138,7 +144,7 @@ function buildEntry(o) {
|
|
|
138
144
|
tags.push({
|
|
139
145
|
tag: "style",
|
|
140
146
|
key: `style-${i}`,
|
|
141
|
-
props: rest,
|
|
147
|
+
props: toProps(rest),
|
|
142
148
|
children
|
|
143
149
|
});
|
|
144
150
|
});
|
|
@@ -160,7 +166,7 @@ function buildEntry(o) {
|
|
|
160
166
|
if (o.base) tags.push({
|
|
161
167
|
tag: "base",
|
|
162
168
|
key: "base",
|
|
163
|
-
props: o.base
|
|
169
|
+
props: toProps(o.base)
|
|
164
170
|
});
|
|
165
171
|
return {
|
|
166
172
|
tags,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-head.d.ts","names":[],"sources":["../../../src/context.ts","../../../src/dom.ts","../../../src/use-head.ts"],"mappings":"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
|
|
1
|
+
{"version":3,"file":"use-head.d.ts","names":[],"sources":["../../../src/context.ts","../../../src/dom.ts","../../../src/use-head.ts"],"mappings":"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"}
|
package/lib/use-head.js
CHANGED
|
@@ -103,6 +103,12 @@ function syncElementAttrs(el, attrs) {
|
|
|
103
103
|
|
|
104
104
|
//#endregion
|
|
105
105
|
//#region src/use-head.ts
|
|
106
|
+
/** Cast a strict tag interface to the internal props format, stripping undefined values */
|
|
107
|
+
function toProps(obj) {
|
|
108
|
+
const result = {};
|
|
109
|
+
for (const [k, v] of Object.entries(obj)) if (v !== void 0) result[k] = v;
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
106
112
|
function buildEntry(o) {
|
|
107
113
|
const tags = [];
|
|
108
114
|
if (o.title != null) tags.push({
|
|
@@ -114,14 +120,14 @@ function buildEntry(o) {
|
|
|
114
120
|
tags.push({
|
|
115
121
|
tag: "meta",
|
|
116
122
|
key: m.name ?? m.property ?? `meta-${i}`,
|
|
117
|
-
props: m
|
|
123
|
+
props: toProps(m)
|
|
118
124
|
});
|
|
119
125
|
});
|
|
120
126
|
o.link?.forEach((l, i) => {
|
|
121
127
|
tags.push({
|
|
122
128
|
tag: "link",
|
|
123
129
|
key: l.href ? `link-${l.rel || ""}-${l.href}` : l.rel ? `link-${l.rel}` : `link-${i}`,
|
|
124
|
-
props: l
|
|
130
|
+
props: toProps(l)
|
|
125
131
|
});
|
|
126
132
|
});
|
|
127
133
|
o.script?.forEach((s, i) => {
|
|
@@ -129,7 +135,7 @@ function buildEntry(o) {
|
|
|
129
135
|
tags.push({
|
|
130
136
|
tag: "script",
|
|
131
137
|
key: s.src ?? `script-${i}`,
|
|
132
|
-
props: rest,
|
|
138
|
+
props: toProps(rest),
|
|
133
139
|
...children != null ? { children } : {}
|
|
134
140
|
});
|
|
135
141
|
});
|
|
@@ -138,7 +144,7 @@ function buildEntry(o) {
|
|
|
138
144
|
tags.push({
|
|
139
145
|
tag: "style",
|
|
140
146
|
key: `style-${i}`,
|
|
141
|
-
props: rest,
|
|
147
|
+
props: toProps(rest),
|
|
142
148
|
children
|
|
143
149
|
});
|
|
144
150
|
});
|
|
@@ -158,7 +164,7 @@ function buildEntry(o) {
|
|
|
158
164
|
if (o.base) tags.push({
|
|
159
165
|
tag: "base",
|
|
160
166
|
key: "base",
|
|
161
|
-
props: o.base
|
|
167
|
+
props: toProps(o.base)
|
|
162
168
|
});
|
|
163
169
|
return {
|
|
164
170
|
tags,
|
package/lib/use-head.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-head.js","names":[],"sources":["../src/context.ts","../src/dom.ts","../src/use-head.ts"],"sourcesContent":["import { createContext } from \"@pyreon/core\"\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface HeadTag {\n /** HTML tag name */\n tag: \"title\" | \"meta\" | \"link\" | \"script\" | \"style\" | \"base\" | \"noscript\"\n /**\n * Deduplication key. Tags with the same key replace each other;\n * innermost component (last added) wins.\n * Example: all components setting the page title use key \"title\".\n */\n key?: string\n /** HTML attributes for the tag */\n props?: Record<string, string>\n /** Text content — for <title>, <script>, <style>, <noscript> */\n children?: string\n}\n\nexport interface UseHeadInput {\n title?: string\n /**\n * Title template — use `%s` as a placeholder for the page title.\n * Applied to the resolved title after deduplication.\n * @example useHead({ titleTemplate: \"%s | My App\" })\n */\n titleTemplate?: string | ((title: string) => string)\n meta?: Record<string, string>[]\n link?: Record<string, string>[]\n script?: ({ src?: string; children?: string } & Record<string, string | undefined>)[]\n style?: ({ children: string } & Record<string, string | undefined>)[]\n noscript?: { children: string }[]\n /** Convenience: emits a <script type=\"application/ld+json\"> tag with JSON.stringify'd content */\n jsonLd?: Record<string, unknown> | Record<string, unknown>[]\n base?: Record<string, string>\n /** Attributes to set on the <html> element (e.g. { lang: \"en\", dir: \"ltr\" }) */\n htmlAttrs?: Record<string, string>\n /** Attributes to set on the <body> element (e.g. { class: \"dark\" }) */\n bodyAttrs?: Record<string, string>\n}\n\n// ─── Context ──────────────────────────────────────────────────────────────────\n\nexport interface HeadEntry {\n tags: HeadTag[]\n titleTemplate?: string | ((title: string) => string) | undefined\n htmlAttrs?: Record<string, string> | undefined\n bodyAttrs?: Record<string, string> | undefined\n}\n\nexport interface HeadContextValue {\n add(id: symbol, entry: HeadEntry): void\n remove(id: symbol): void\n /** Returns deduplicated tags — last-added entry wins per key */\n resolve(): HeadTag[]\n /** Returns the merged titleTemplate (last-added wins) */\n resolveTitleTemplate(): (string | ((title: string) => string)) | undefined\n /** Returns merged htmlAttrs (later entries override earlier) */\n resolveHtmlAttrs(): Record<string, string>\n /** Returns merged bodyAttrs (later entries override earlier) */\n resolveBodyAttrs(): Record<string, string>\n}\n\nexport function createHeadContext(): HeadContextValue {\n const map = new Map<symbol, HeadEntry>()\n\n // ── Cached resolve ───────────────────────────────────────────────────────\n let dirty = true\n let cachedTags: HeadTag[] = []\n let cachedTitleTemplate: (string | ((title: string) => string)) | undefined\n let cachedHtmlAttrs: Record<string, string> = {}\n let cachedBodyAttrs: Record<string, string> = {}\n\n function rebuild(): void {\n if (!dirty) return\n dirty = false\n\n const keyed = new Map<string, HeadTag>()\n const unkeyed: HeadTag[] = []\n let titleTemplate: (string | ((title: string) => string)) | undefined\n const htmlAttrs: Record<string, string> = {}\n const bodyAttrs: Record<string, string> = {}\n\n for (const entry of map.values()) {\n for (const tag of entry.tags) {\n if (tag.key) keyed.set(tag.key, tag)\n else unkeyed.push(tag)\n }\n if (entry.titleTemplate !== undefined) titleTemplate = entry.titleTemplate\n if (entry.htmlAttrs) Object.assign(htmlAttrs, entry.htmlAttrs)\n if (entry.bodyAttrs) Object.assign(bodyAttrs, entry.bodyAttrs)\n }\n\n cachedTags = [...keyed.values(), ...unkeyed]\n cachedTitleTemplate = titleTemplate\n cachedHtmlAttrs = htmlAttrs\n cachedBodyAttrs = bodyAttrs\n }\n\n return {\n add(id, entry) {\n map.set(id, entry)\n dirty = true\n },\n remove(id) {\n map.delete(id)\n dirty = true\n },\n resolve() {\n rebuild()\n return cachedTags\n },\n resolveTitleTemplate() {\n rebuild()\n return cachedTitleTemplate\n },\n resolveHtmlAttrs() {\n rebuild()\n return cachedHtmlAttrs\n },\n resolveBodyAttrs() {\n rebuild()\n return cachedBodyAttrs\n },\n }\n}\n\nexport const HeadContext = createContext<HeadContextValue | null>(null)\n","import type { HeadContextValue } from \"./context\"\n\nconst ATTR = \"data-pyreon-head\"\n\n/** Tracks managed elements by key — avoids querySelectorAll on every sync */\nconst managedElements = new Map<string, Element>()\n\n/**\n * Sync the resolved head tags to the real DOM <head>.\n * Uses incremental diffing: matches existing elements by key, patches attributes\n * in-place, adds new elements, and removes stale ones.\n * Also syncs htmlAttrs, bodyAttrs, and applies titleTemplate.\n * No-op on the server (typeof document === \"undefined\").\n */\nfunction patchExistingTag(\n found: Element,\n tag: { props: Record<string, unknown>; children: string },\n kept: Set<string>,\n): void {\n kept.add(found.getAttribute(ATTR) as string)\n patchAttrs(found, tag.props as Record<string, string>)\n const content = String(tag.children)\n if (found.textContent !== content) found.textContent = content\n}\n\nfunction createNewTag(tag: {\n tag: string\n props: Record<string, unknown>\n children: string\n key: unknown\n}): void {\n const el = document.createElement(tag.tag)\n const key = tag.key as string\n el.setAttribute(ATTR, key)\n for (const [k, v] of Object.entries(tag.props as Record<string, string>)) {\n el.setAttribute(k, v)\n }\n if (tag.children) el.textContent = tag.children\n document.head.appendChild(el)\n managedElements.set(key, el)\n}\n\nexport function syncDom(ctx: HeadContextValue): void {\n if (typeof document === \"undefined\") return\n\n const tags = ctx.resolve()\n const titleTemplate = ctx.resolveTitleTemplate()\n\n // Seed from DOM on first sync, or re-seed if DOM was reset (e.g. between tests)\n let needsSeed = managedElements.size === 0\n if (!needsSeed) {\n // Check if a tracked element is still in the DOM\n const sample = managedElements.values().next().value\n if (sample && !sample.isConnected) {\n managedElements.clear()\n needsSeed = true\n }\n }\n if (needsSeed) {\n const existing = document.head.querySelectorAll(`[${ATTR}]`)\n for (const el of existing) {\n managedElements.set(el.getAttribute(ATTR) as string, el)\n }\n }\n\n const kept = new Set<string>()\n\n for (const tag of tags) {\n if (tag.tag === \"title\") {\n document.title = applyTitleTemplate(String(tag.children), titleTemplate)\n continue\n }\n\n const key = tag.key as string\n const found = managedElements.get(key)\n\n if (found && found.tagName.toLowerCase() === tag.tag) {\n patchExistingTag(found, tag as { props: Record<string, unknown>; children: string }, kept)\n } else {\n if (found) {\n found.remove()\n managedElements.delete(key)\n }\n createNewTag(\n tag as { tag: string; props: Record<string, unknown>; children: string; key: unknown },\n )\n kept.add(key)\n }\n }\n\n // Remove stale elements\n for (const [key, el] of managedElements) {\n if (!kept.has(key)) {\n el.remove()\n managedElements.delete(key)\n }\n }\n\n syncElementAttrs(document.documentElement, ctx.resolveHtmlAttrs())\n syncElementAttrs(document.body, ctx.resolveBodyAttrs())\n}\n\n/** Patch an element's attributes to match the desired props. */\nfunction patchAttrs(el: Element, props: Record<string, string>): void {\n for (let i = el.attributes.length - 1; i >= 0; i--) {\n const attr = el.attributes[i]\n if (!attr || attr.name === ATTR) continue\n if (!(attr.name in props)) el.removeAttribute(attr.name)\n }\n for (const [k, v] of Object.entries(props)) {\n if (el.getAttribute(k) !== v) el.setAttribute(k, v)\n }\n}\n\nfunction applyTitleTemplate(\n title: string,\n template: string | ((t: string) => string) | undefined,\n): string {\n if (!template) return title\n if (typeof template === \"function\") return template(title)\n return template.replace(/%s/g, title)\n}\n\n/** Sync pyreon-managed attributes on <html> or <body>. */\nfunction syncElementAttrs(el: Element, attrs: Record<string, string>): void {\n // Remove previously managed attrs that are no longer present\n const managed = el.getAttribute(`${ATTR}-attrs`)\n if (managed) {\n for (const name of managed.split(\",\")) {\n if (name && !(name in attrs)) el.removeAttribute(name)\n }\n }\n const keys: string[] = []\n for (const [k, v] of Object.entries(attrs)) {\n keys.push(k)\n if (el.getAttribute(k) !== v) el.setAttribute(k, v)\n }\n if (keys.length > 0) {\n el.setAttribute(`${ATTR}-attrs`, keys.join(\",\"))\n } else if (managed) {\n el.removeAttribute(`${ATTR}-attrs`)\n }\n}\n","import { onMount, onUnmount, useContext } from \"@pyreon/core\"\nimport { effect } from \"@pyreon/reactivity\"\nimport type { HeadEntry, HeadTag, UseHeadInput } from \"./context\"\nimport { HeadContext } from \"./context\"\nimport { syncDom } from \"./dom\"\n\nfunction buildEntry(o: UseHeadInput): HeadEntry {\n const tags: HeadTag[] = []\n if (o.title != null) tags.push({ tag: \"title\", key: \"title\", children: o.title })\n o.meta?.forEach((m, i) => {\n tags.push({\n tag: \"meta\",\n key: m.name ?? m.property ?? `meta-${i}`,\n props: m,\n })\n })\n o.link?.forEach((l, i) => {\n tags.push({\n tag: \"link\",\n key: l.href ? `link-${l.rel || \"\"}-${l.href}` : l.rel ? `link-${l.rel}` : `link-${i}`,\n props: l,\n })\n })\n o.script?.forEach((s, i) => {\n const { children, ...rest } = s\n tags.push({\n tag: \"script\",\n key: s.src ?? `script-${i}`,\n props: rest as Record<string, string>,\n ...(children != null ? { children } : {}),\n })\n })\n o.style?.forEach((s, i) => {\n const { children, ...rest } = s\n tags.push({\n tag: \"style\",\n key: `style-${i}`,\n props: rest as Record<string, string>,\n children,\n })\n })\n o.noscript?.forEach((ns, i) => {\n tags.push({ tag: \"noscript\", key: `noscript-${i}`, children: ns.children })\n })\n if (o.jsonLd) {\n tags.push({\n tag: \"script\",\n key: \"jsonld\",\n props: { type: \"application/ld+json\" },\n children: JSON.stringify(o.jsonLd),\n })\n }\n if (o.base) tags.push({ tag: \"base\", key: \"base\", props: o.base })\n return {\n tags,\n titleTemplate: o.titleTemplate,\n htmlAttrs: o.htmlAttrs,\n bodyAttrs: o.bodyAttrs,\n }\n}\n\n/**\n * Register head tags (title, meta, link, script, style, noscript, base, jsonLd)\n * for the current component.\n *\n * Accepts a static object or a reactive getter:\n * useHead({ title: \"My Page\", meta: [{ name: \"description\", content: \"...\" }] })\n * useHead(() => ({ title: `${count()} items` })) // updates when signal changes\n *\n * Tags are deduplicated by key — innermost component wins.\n * Requires a <HeadProvider> (CSR) or renderWithHead() (SSR) ancestor.\n */\nexport function useHead(input: UseHeadInput | (() => UseHeadInput)): void {\n const ctx = useContext(HeadContext)\n if (!ctx) return // no HeadProvider — silently no-op\n\n const id = Symbol()\n\n if (typeof input === \"function\") {\n if (typeof document !== \"undefined\") {\n // CSR: reactive — re-register whenever signals change\n effect(() => {\n ctx.add(id, buildEntry(input()))\n syncDom(ctx)\n })\n } else {\n // SSR: evaluate once synchronously (no effects on server)\n ctx.add(id, buildEntry(input()))\n }\n } else {\n ctx.add(id, buildEntry(input))\n onMount(() => {\n syncDom(ctx)\n return undefined\n })\n }\n\n onUnmount(() => {\n ctx.remove(id)\n syncDom(ctx)\n })\n}\n"],"mappings":";;;;AA+HA,MAAa,cAAc,cAAuC,KAAK;;;;AC7HvE,MAAM,OAAO;;AAGb,MAAM,kCAAkB,IAAI,KAAsB;;;;;;;;AASlD,SAAS,iBACP,OACA,KACA,MACM;AACN,MAAK,IAAI,MAAM,aAAa,KAAK,CAAW;AAC5C,YAAW,OAAO,IAAI,MAAgC;CACtD,MAAM,UAAU,OAAO,IAAI,SAAS;AACpC,KAAI,MAAM,gBAAgB,QAAS,OAAM,cAAc;;AAGzD,SAAS,aAAa,KAKb;CACP,MAAM,KAAK,SAAS,cAAc,IAAI,IAAI;CAC1C,MAAM,MAAM,IAAI;AAChB,IAAG,aAAa,MAAM,IAAI;AAC1B,MAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,IAAI,MAAgC,CACtE,IAAG,aAAa,GAAG,EAAE;AAEvB,KAAI,IAAI,SAAU,IAAG,cAAc,IAAI;AACvC,UAAS,KAAK,YAAY,GAAG;AAC7B,iBAAgB,IAAI,KAAK,GAAG;;AAG9B,SAAgB,QAAQ,KAA6B;AACnD,KAAI,OAAO,aAAa,YAAa;CAErC,MAAM,OAAO,IAAI,SAAS;CAC1B,MAAM,gBAAgB,IAAI,sBAAsB;CAGhD,IAAI,YAAY,gBAAgB,SAAS;AACzC,KAAI,CAAC,WAAW;EAEd,MAAM,SAAS,gBAAgB,QAAQ,CAAC,MAAM,CAAC;AAC/C,MAAI,UAAU,CAAC,OAAO,aAAa;AACjC,mBAAgB,OAAO;AACvB,eAAY;;;AAGhB,KAAI,WAAW;EACb,MAAM,WAAW,SAAS,KAAK,iBAAiB,IAAI,KAAK,GAAG;AAC5D,OAAK,MAAM,MAAM,SACf,iBAAgB,IAAI,GAAG,aAAa,KAAK,EAAY,GAAG;;CAI5D,MAAM,uBAAO,IAAI,KAAa;AAE9B,MAAK,MAAM,OAAO,MAAM;AACtB,MAAI,IAAI,QAAQ,SAAS;AACvB,YAAS,QAAQ,mBAAmB,OAAO,IAAI,SAAS,EAAE,cAAc;AACxE;;EAGF,MAAM,MAAM,IAAI;EAChB,MAAM,QAAQ,gBAAgB,IAAI,IAAI;AAEtC,MAAI,SAAS,MAAM,QAAQ,aAAa,KAAK,IAAI,IAC/C,kBAAiB,OAAO,KAA6D,KAAK;OACrF;AACL,OAAI,OAAO;AACT,UAAM,QAAQ;AACd,oBAAgB,OAAO,IAAI;;AAE7B,gBACE,IACD;AACD,QAAK,IAAI,IAAI;;;AAKjB,MAAK,MAAM,CAAC,KAAK,OAAO,gBACtB,KAAI,CAAC,KAAK,IAAI,IAAI,EAAE;AAClB,KAAG,QAAQ;AACX,kBAAgB,OAAO,IAAI;;AAI/B,kBAAiB,SAAS,iBAAiB,IAAI,kBAAkB,CAAC;AAClE,kBAAiB,SAAS,MAAM,IAAI,kBAAkB,CAAC;;;AAIzD,SAAS,WAAW,IAAa,OAAqC;AACpE,MAAK,IAAI,IAAI,GAAG,WAAW,SAAS,GAAG,KAAK,GAAG,KAAK;EAClD,MAAM,OAAO,GAAG,WAAW;AAC3B,MAAI,CAAC,QAAQ,KAAK,SAAS,KAAM;AACjC,MAAI,EAAE,KAAK,QAAQ,OAAQ,IAAG,gBAAgB,KAAK,KAAK;;AAE1D,MAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,MAAM,CACxC,KAAI,GAAG,aAAa,EAAE,KAAK,EAAG,IAAG,aAAa,GAAG,EAAE;;AAIvD,SAAS,mBACP,OACA,UACQ;AACR,KAAI,CAAC,SAAU,QAAO;AACtB,KAAI,OAAO,aAAa,WAAY,QAAO,SAAS,MAAM;AAC1D,QAAO,SAAS,QAAQ,OAAO,MAAM;;;AAIvC,SAAS,iBAAiB,IAAa,OAAqC;CAE1E,MAAM,UAAU,GAAG,aAAa,GAAG,KAAK,QAAQ;AAChD,KAAI,SACF;OAAK,MAAM,QAAQ,QAAQ,MAAM,IAAI,CACnC,KAAI,QAAQ,EAAE,QAAQ,OAAQ,IAAG,gBAAgB,KAAK;;CAG1D,MAAM,OAAiB,EAAE;AACzB,MAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,MAAM,EAAE;AAC1C,OAAK,KAAK,EAAE;AACZ,MAAI,GAAG,aAAa,EAAE,KAAK,EAAG,IAAG,aAAa,GAAG,EAAE;;AAErD,KAAI,KAAK,SAAS,EAChB,IAAG,aAAa,GAAG,KAAK,SAAS,KAAK,KAAK,IAAI,CAAC;UACvC,QACT,IAAG,gBAAgB,GAAG,KAAK,QAAQ;;;;;ACtIvC,SAAS,WAAW,GAA4B;CAC9C,MAAM,OAAkB,EAAE;AAC1B,KAAI,EAAE,SAAS,KAAM,MAAK,KAAK;EAAE,KAAK;EAAS,KAAK;EAAS,UAAU,EAAE;EAAO,CAAC;AACjF,GAAE,MAAM,SAAS,GAAG,MAAM;AACxB,OAAK,KAAK;GACR,KAAK;GACL,KAAK,EAAE,QAAQ,EAAE,YAAY,QAAQ;GACrC,OAAO;GACR,CAAC;GACF;AACF,GAAE,MAAM,SAAS,GAAG,MAAM;AACxB,OAAK,KAAK;GACR,KAAK;GACL,KAAK,EAAE,OAAO,QAAQ,EAAE,OAAO,GAAG,GAAG,EAAE,SAAS,EAAE,MAAM,QAAQ,EAAE,QAAQ,QAAQ;GAClF,OAAO;GACR,CAAC;GACF;AACF,GAAE,QAAQ,SAAS,GAAG,MAAM;EAC1B,MAAM,EAAE,UAAU,GAAG,SAAS;AAC9B,OAAK,KAAK;GACR,KAAK;GACL,KAAK,EAAE,OAAO,UAAU;GACxB,OAAO;GACP,GAAI,YAAY,OAAO,EAAE,UAAU,GAAG,EAAE;GACzC,CAAC;GACF;AACF,GAAE,OAAO,SAAS,GAAG,MAAM;EACzB,MAAM,EAAE,UAAU,GAAG,SAAS;AAC9B,OAAK,KAAK;GACR,KAAK;GACL,KAAK,SAAS;GACd,OAAO;GACP;GACD,CAAC;GACF;AACF,GAAE,UAAU,SAAS,IAAI,MAAM;AAC7B,OAAK,KAAK;GAAE,KAAK;GAAY,KAAK,YAAY;GAAK,UAAU,GAAG;GAAU,CAAC;GAC3E;AACF,KAAI,EAAE,OACJ,MAAK,KAAK;EACR,KAAK;EACL,KAAK;EACL,OAAO,EAAE,MAAM,uBAAuB;EACtC,UAAU,KAAK,UAAU,EAAE,OAAO;EACnC,CAAC;AAEJ,KAAI,EAAE,KAAM,MAAK,KAAK;EAAE,KAAK;EAAQ,KAAK;EAAQ,OAAO,EAAE;EAAM,CAAC;AAClE,QAAO;EACL;EACA,eAAe,EAAE;EACjB,WAAW,EAAE;EACb,WAAW,EAAE;EACd;;;;;;;;;;;;;AAcH,SAAgB,QAAQ,OAAkD;CACxE,MAAM,MAAM,WAAW,YAAY;AACnC,KAAI,CAAC,IAAK;CAEV,MAAM,KAAK,QAAQ;AAEnB,KAAI,OAAO,UAAU,WACnB,KAAI,OAAO,aAAa,YAEtB,cAAa;AACX,MAAI,IAAI,IAAI,WAAW,OAAO,CAAC,CAAC;AAChC,UAAQ,IAAI;GACZ;KAGF,KAAI,IAAI,IAAI,WAAW,OAAO,CAAC,CAAC;MAE7B;AACL,MAAI,IAAI,IAAI,WAAW,MAAM,CAAC;AAC9B,gBAAc;AACZ,WAAQ,IAAI;IAEZ;;AAGJ,iBAAgB;AACd,MAAI,OAAO,GAAG;AACd,UAAQ,IAAI;GACZ"}
|
|
1
|
+
{"version":3,"file":"use-head.js","names":[],"sources":["../src/context.ts","../src/dom.ts","../src/use-head.ts"],"sourcesContent":["import { createContext } from \"@pyreon/core\"\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface HeadTag {\n /** HTML tag name */\n tag: \"title\" | \"meta\" | \"link\" | \"script\" | \"style\" | \"base\" | \"noscript\"\n /**\n * Deduplication key. Tags with the same key replace each other;\n * innermost component (last added) wins.\n * Example: all components setting the page title use key \"title\".\n */\n key?: string\n /** HTML attributes for the tag */\n props?: Record<string, string>\n /** Text content — for <title>, <script>, <style>, <noscript> */\n children?: string\n}\n\n// ─── Strict tag types ────────────────────────────────────────────────────────\n\n/** Standard `<meta>` tag attributes. Catches typos like `{ naem: \"description\" }`. */\nexport interface MetaTag {\n /** Standard meta name (e.g. \"description\", \"viewport\", \"robots\") */\n name?: string\n /** Open Graph / social property (e.g. \"og:title\", \"twitter:card\") */\n property?: string\n /** HTTP equivalent header (e.g. \"refresh\", \"content-type\") */\n \"http-equiv\"?: string\n /** Value associated with name, property, or http-equiv */\n content?: string\n /** Document character encoding (e.g. \"utf-8\") */\n charset?: string\n /** Schema.org itemprop */\n itemprop?: string\n /** Media condition for applicability (e.g. \"(prefers-color-scheme: dark)\") */\n media?: string\n}\n\n/** Standard `<link>` tag attributes. */\nexport interface LinkTag {\n /** Relationship to the current document (e.g. \"stylesheet\", \"icon\", \"canonical\") */\n rel?: string\n /** URL of the linked resource */\n href?: string\n /** Resource type hint for preloading (e.g. \"style\", \"script\", \"font\") */\n as?: string\n /** MIME type (e.g. \"text/css\", \"image/png\") */\n type?: string\n /** Media query for conditional loading */\n media?: string\n /** CORS mode */\n crossorigin?: string\n /** Subresource integrity hash */\n integrity?: string\n /** Icon sizes (e.g. \"32x32\", \"any\") */\n sizes?: string\n /** Language of the linked resource */\n hreflang?: string\n /** Title for the link (used for alternate stylesheets) */\n title?: string\n /** Fetch priority hint */\n fetchpriority?: \"high\" | \"low\" | \"auto\"\n /** Referrer policy */\n referrerpolicy?: string\n /** Image source set for preloading responsive images */\n imagesrcset?: string\n /** Image sizes for preloading responsive images */\n imagesizes?: string\n /** Disable the resource (for stylesheets) */\n disabled?: string\n /** Color for mask-icon */\n color?: string\n}\n\n/** Standard `<script>` tag attributes. */\nexport interface ScriptTag {\n /** External script URL */\n src?: string\n /** Script MIME type or module type (e.g. \"module\", \"importmap\") */\n type?: string\n /** Load asynchronously */\n async?: string\n /** Defer execution until document is parsed */\n defer?: string\n /** CORS mode */\n crossorigin?: string\n /** Subresource integrity hash */\n integrity?: string\n /** Exclude from module-supporting browsers */\n nomodule?: string\n /** Referrer policy */\n referrerpolicy?: string\n /** Fetch priority hint */\n fetchpriority?: string\n /** Inline script content */\n children?: string\n}\n\n/** Standard `<style>` tag attributes. */\nexport interface StyleTag {\n /** Inline CSS content (required) */\n children: string\n /** Media query for conditional styles */\n media?: string\n /** Nonce for CSP */\n nonce?: string\n /** Title for alternate stylesheets */\n title?: string\n /** Render-blocking behavior */\n blocking?: string\n}\n\n/** Standard `<base>` tag attributes. */\nexport interface BaseTag {\n /** Base URL for relative URLs in the document */\n href?: string\n /** Default target for links and forms */\n target?: \"_blank\" | \"_self\" | \"_parent\" | \"_top\"\n}\n\nexport interface UseHeadInput {\n title?: string\n /**\n * Title template — use `%s` as a placeholder for the page title.\n * Applied to the resolved title after deduplication.\n * @example useHead({ titleTemplate: \"%s | My App\" })\n */\n titleTemplate?: string | ((title: string) => string)\n meta?: MetaTag[]\n link?: LinkTag[]\n script?: ScriptTag[]\n style?: StyleTag[]\n noscript?: { children: string }[]\n /** Convenience: emits a <script type=\"application/ld+json\"> tag with JSON.stringify'd content */\n jsonLd?: Record<string, unknown> | Record<string, unknown>[]\n base?: BaseTag\n /** Attributes to set on the <html> element (e.g. { lang: \"en\", dir: \"ltr\" }) */\n htmlAttrs?: Record<string, string>\n /** Attributes to set on the <body> element (e.g. { class: \"dark\" }) */\n bodyAttrs?: Record<string, string>\n}\n\n// ─── Context ──────────────────────────────────────────────────────────────────\n\nexport interface HeadEntry {\n tags: HeadTag[]\n titleTemplate?: string | ((title: string) => string) | undefined\n htmlAttrs?: Record<string, string> | undefined\n bodyAttrs?: Record<string, string> | undefined\n}\n\nexport interface HeadContextValue {\n add(id: symbol, entry: HeadEntry): void\n remove(id: symbol): void\n /** Returns deduplicated tags — last-added entry wins per key */\n resolve(): HeadTag[]\n /** Returns the merged titleTemplate (last-added wins) */\n resolveTitleTemplate(): (string | ((title: string) => string)) | undefined\n /** Returns merged htmlAttrs (later entries override earlier) */\n resolveHtmlAttrs(): Record<string, string>\n /** Returns merged bodyAttrs (later entries override earlier) */\n resolveBodyAttrs(): Record<string, string>\n}\n\nexport function createHeadContext(): HeadContextValue {\n const map = new Map<symbol, HeadEntry>()\n\n // ── Cached resolve ───────────────────────────────────────────────────────\n let dirty = true\n let cachedTags: HeadTag[] = []\n let cachedTitleTemplate: (string | ((title: string) => string)) | undefined\n let cachedHtmlAttrs: Record<string, string> = {}\n let cachedBodyAttrs: Record<string, string> = {}\n\n function rebuild(): void {\n if (!dirty) return\n dirty = false\n\n const keyed = new Map<string, HeadTag>()\n const unkeyed: HeadTag[] = []\n let titleTemplate: (string | ((title: string) => string)) | undefined\n const htmlAttrs: Record<string, string> = {}\n const bodyAttrs: Record<string, string> = {}\n\n for (const entry of map.values()) {\n for (const tag of entry.tags) {\n if (tag.key) keyed.set(tag.key, tag)\n else unkeyed.push(tag)\n }\n if (entry.titleTemplate !== undefined) titleTemplate = entry.titleTemplate\n if (entry.htmlAttrs) Object.assign(htmlAttrs, entry.htmlAttrs)\n if (entry.bodyAttrs) Object.assign(bodyAttrs, entry.bodyAttrs)\n }\n\n cachedTags = [...keyed.values(), ...unkeyed]\n cachedTitleTemplate = titleTemplate\n cachedHtmlAttrs = htmlAttrs\n cachedBodyAttrs = bodyAttrs\n }\n\n return {\n add(id, entry) {\n map.set(id, entry)\n dirty = true\n },\n remove(id) {\n map.delete(id)\n dirty = true\n },\n resolve() {\n rebuild()\n return cachedTags\n },\n resolveTitleTemplate() {\n rebuild()\n return cachedTitleTemplate\n },\n resolveHtmlAttrs() {\n rebuild()\n return cachedHtmlAttrs\n },\n resolveBodyAttrs() {\n rebuild()\n return cachedBodyAttrs\n },\n }\n}\n\nexport const HeadContext = createContext<HeadContextValue | null>(null)\n","import type { HeadContextValue } from \"./context\"\n\nconst ATTR = \"data-pyreon-head\"\n\n/** Tracks managed elements by key — avoids querySelectorAll on every sync */\nconst managedElements = new Map<string, Element>()\n\n/**\n * Sync the resolved head tags to the real DOM <head>.\n * Uses incremental diffing: matches existing elements by key, patches attributes\n * in-place, adds new elements, and removes stale ones.\n * Also syncs htmlAttrs, bodyAttrs, and applies titleTemplate.\n * No-op on the server (typeof document === \"undefined\").\n */\nfunction patchExistingTag(\n found: Element,\n tag: { props: Record<string, unknown>; children: string },\n kept: Set<string>,\n): void {\n kept.add(found.getAttribute(ATTR) as string)\n patchAttrs(found, tag.props as Record<string, string>)\n const content = String(tag.children)\n if (found.textContent !== content) found.textContent = content\n}\n\nfunction createNewTag(tag: {\n tag: string\n props: Record<string, unknown>\n children: string\n key: unknown\n}): void {\n const el = document.createElement(tag.tag)\n const key = tag.key as string\n el.setAttribute(ATTR, key)\n for (const [k, v] of Object.entries(tag.props as Record<string, string>)) {\n el.setAttribute(k, v)\n }\n if (tag.children) el.textContent = tag.children\n document.head.appendChild(el)\n managedElements.set(key, el)\n}\n\nexport function syncDom(ctx: HeadContextValue): void {\n if (typeof document === \"undefined\") return\n\n const tags = ctx.resolve()\n const titleTemplate = ctx.resolveTitleTemplate()\n\n // Seed from DOM on first sync, or re-seed if DOM was reset (e.g. between tests)\n let needsSeed = managedElements.size === 0\n if (!needsSeed) {\n // Check if a tracked element is still in the DOM\n const sample = managedElements.values().next().value\n if (sample && !sample.isConnected) {\n managedElements.clear()\n needsSeed = true\n }\n }\n if (needsSeed) {\n const existing = document.head.querySelectorAll(`[${ATTR}]`)\n for (const el of existing) {\n managedElements.set(el.getAttribute(ATTR) as string, el)\n }\n }\n\n const kept = new Set<string>()\n\n for (const tag of tags) {\n if (tag.tag === \"title\") {\n document.title = applyTitleTemplate(String(tag.children), titleTemplate)\n continue\n }\n\n const key = tag.key as string\n const found = managedElements.get(key)\n\n if (found && found.tagName.toLowerCase() === tag.tag) {\n patchExistingTag(found, tag as { props: Record<string, unknown>; children: string }, kept)\n } else {\n if (found) {\n found.remove()\n managedElements.delete(key)\n }\n createNewTag(\n tag as { tag: string; props: Record<string, unknown>; children: string; key: unknown },\n )\n kept.add(key)\n }\n }\n\n // Remove stale elements\n for (const [key, el] of managedElements) {\n if (!kept.has(key)) {\n el.remove()\n managedElements.delete(key)\n }\n }\n\n syncElementAttrs(document.documentElement, ctx.resolveHtmlAttrs())\n syncElementAttrs(document.body, ctx.resolveBodyAttrs())\n}\n\n/** Patch an element's attributes to match the desired props. */\nfunction patchAttrs(el: Element, props: Record<string, string>): void {\n for (let i = el.attributes.length - 1; i >= 0; i--) {\n const attr = el.attributes[i]\n if (!attr || attr.name === ATTR) continue\n if (!(attr.name in props)) el.removeAttribute(attr.name)\n }\n for (const [k, v] of Object.entries(props)) {\n if (el.getAttribute(k) !== v) el.setAttribute(k, v)\n }\n}\n\nfunction applyTitleTemplate(\n title: string,\n template: string | ((t: string) => string) | undefined,\n): string {\n if (!template) return title\n if (typeof template === \"function\") return template(title)\n return template.replace(/%s/g, title)\n}\n\n/** Sync pyreon-managed attributes on <html> or <body>. */\nfunction syncElementAttrs(el: Element, attrs: Record<string, string>): void {\n // Remove previously managed attrs that are no longer present\n const managed = el.getAttribute(`${ATTR}-attrs`)\n if (managed) {\n for (const name of managed.split(\",\")) {\n if (name && !(name in attrs)) el.removeAttribute(name)\n }\n }\n const keys: string[] = []\n for (const [k, v] of Object.entries(attrs)) {\n keys.push(k)\n if (el.getAttribute(k) !== v) el.setAttribute(k, v)\n }\n if (keys.length > 0) {\n el.setAttribute(`${ATTR}-attrs`, keys.join(\",\"))\n } else if (managed) {\n el.removeAttribute(`${ATTR}-attrs`)\n }\n}\n","import { onMount, onUnmount, useContext } from \"@pyreon/core\"\nimport { effect } from \"@pyreon/reactivity\"\nimport type { HeadEntry, HeadTag, UseHeadInput } from \"./context\"\nimport { HeadContext } from \"./context\"\nimport { syncDom } from \"./dom\"\n\n/** Cast a strict tag interface to the internal props format, stripping undefined values */\nfunction toProps(obj: Record<string, string | undefined>): Record<string, string> {\n const result: Record<string, string> = {}\n for (const [k, v] of Object.entries(obj)) {\n if (v !== undefined) result[k] = v\n }\n return result\n}\n\nfunction buildEntry(o: UseHeadInput): HeadEntry {\n const tags: HeadTag[] = []\n if (o.title != null) tags.push({ tag: \"title\", key: \"title\", children: o.title })\n o.meta?.forEach((m, i) => {\n tags.push({\n tag: \"meta\",\n key: m.name ?? m.property ?? `meta-${i}`,\n props: toProps(m as Record<string, string | undefined>),\n })\n })\n o.link?.forEach((l, i) => {\n tags.push({\n tag: \"link\",\n key: l.href ? `link-${l.rel || \"\"}-${l.href}` : l.rel ? `link-${l.rel}` : `link-${i}`,\n props: toProps(l as Record<string, string | undefined>),\n })\n })\n o.script?.forEach((s, i) => {\n const { children, ...rest } = s\n tags.push({\n tag: \"script\",\n key: s.src ?? `script-${i}`,\n props: toProps(rest as Record<string, string | undefined>),\n ...(children != null ? { children } : {}),\n })\n })\n o.style?.forEach((s, i) => {\n const { children, ...rest } = s\n tags.push({\n tag: \"style\",\n key: `style-${i}`,\n props: toProps(rest as Record<string, string | undefined>),\n children,\n })\n })\n o.noscript?.forEach((ns, i) => {\n tags.push({ tag: \"noscript\", key: `noscript-${i}`, children: ns.children })\n })\n if (o.jsonLd) {\n tags.push({\n tag: \"script\",\n key: \"jsonld\",\n props: { type: \"application/ld+json\" },\n children: JSON.stringify(o.jsonLd),\n })\n }\n if (o.base)\n tags.push({\n tag: \"base\",\n key: \"base\",\n props: toProps(o.base as Record<string, string | undefined>),\n })\n return {\n tags,\n titleTemplate: o.titleTemplate,\n htmlAttrs: o.htmlAttrs,\n bodyAttrs: o.bodyAttrs,\n }\n}\n\n/**\n * Register head tags (title, meta, link, script, style, noscript, base, jsonLd)\n * for the current component.\n *\n * Accepts a static object or a reactive getter:\n * useHead({ title: \"My Page\", meta: [{ name: \"description\", content: \"...\" }] })\n * useHead(() => ({ title: `${count()} items` })) // updates when signal changes\n *\n * Tags are deduplicated by key — innermost component wins.\n * Requires a <HeadProvider> (CSR) or renderWithHead() (SSR) ancestor.\n */\nexport function useHead(input: UseHeadInput | (() => UseHeadInput)): void {\n const ctx = useContext(HeadContext)\n if (!ctx) return // no HeadProvider — silently no-op\n\n const id = Symbol()\n\n if (typeof input === \"function\") {\n if (typeof document !== \"undefined\") {\n // CSR: reactive — re-register whenever signals change\n effect(() => {\n ctx.add(id, buildEntry(input()))\n syncDom(ctx)\n })\n } else {\n // SSR: evaluate once synchronously (no effects on server)\n ctx.add(id, buildEntry(input()))\n }\n } else {\n ctx.add(id, buildEntry(input))\n onMount(() => {\n syncDom(ctx)\n return undefined\n })\n }\n\n onUnmount(() => {\n ctx.remove(id)\n syncDom(ctx)\n })\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/head",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
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.
|
|
58
|
-
"@pyreon/reactivity": "^0.5.
|
|
59
|
-
"@pyreon/runtime-server": "^0.5.
|
|
57
|
+
"@pyreon/core": "^0.5.5",
|
|
58
|
+
"@pyreon/reactivity": "^0.5.5",
|
|
59
|
+
"@pyreon/runtime-server": "^0.5.5"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@happy-dom/global-registrator": "*",
|
package/src/context.ts
CHANGED
|
@@ -17,6 +17,108 @@ export interface HeadTag {
|
|
|
17
17
|
children?: string
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
// ─── Strict tag types ────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/** Standard `<meta>` tag attributes. Catches typos like `{ naem: "description" }`. */
|
|
23
|
+
export interface MetaTag {
|
|
24
|
+
/** Standard meta name (e.g. "description", "viewport", "robots") */
|
|
25
|
+
name?: string
|
|
26
|
+
/** Open Graph / social property (e.g. "og:title", "twitter:card") */
|
|
27
|
+
property?: string
|
|
28
|
+
/** HTTP equivalent header (e.g. "refresh", "content-type") */
|
|
29
|
+
"http-equiv"?: string
|
|
30
|
+
/** Value associated with name, property, or http-equiv */
|
|
31
|
+
content?: string
|
|
32
|
+
/** Document character encoding (e.g. "utf-8") */
|
|
33
|
+
charset?: string
|
|
34
|
+
/** Schema.org itemprop */
|
|
35
|
+
itemprop?: string
|
|
36
|
+
/** Media condition for applicability (e.g. "(prefers-color-scheme: dark)") */
|
|
37
|
+
media?: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Standard `<link>` tag attributes. */
|
|
41
|
+
export interface LinkTag {
|
|
42
|
+
/** Relationship to the current document (e.g. "stylesheet", "icon", "canonical") */
|
|
43
|
+
rel?: string
|
|
44
|
+
/** URL of the linked resource */
|
|
45
|
+
href?: string
|
|
46
|
+
/** Resource type hint for preloading (e.g. "style", "script", "font") */
|
|
47
|
+
as?: string
|
|
48
|
+
/** MIME type (e.g. "text/css", "image/png") */
|
|
49
|
+
type?: string
|
|
50
|
+
/** Media query for conditional loading */
|
|
51
|
+
media?: string
|
|
52
|
+
/** CORS mode */
|
|
53
|
+
crossorigin?: string
|
|
54
|
+
/** Subresource integrity hash */
|
|
55
|
+
integrity?: string
|
|
56
|
+
/** Icon sizes (e.g. "32x32", "any") */
|
|
57
|
+
sizes?: string
|
|
58
|
+
/** Language of the linked resource */
|
|
59
|
+
hreflang?: string
|
|
60
|
+
/** Title for the link (used for alternate stylesheets) */
|
|
61
|
+
title?: string
|
|
62
|
+
/** Fetch priority hint */
|
|
63
|
+
fetchpriority?: "high" | "low" | "auto"
|
|
64
|
+
/** Referrer policy */
|
|
65
|
+
referrerpolicy?: string
|
|
66
|
+
/** Image source set for preloading responsive images */
|
|
67
|
+
imagesrcset?: string
|
|
68
|
+
/** Image sizes for preloading responsive images */
|
|
69
|
+
imagesizes?: string
|
|
70
|
+
/** Disable the resource (for stylesheets) */
|
|
71
|
+
disabled?: string
|
|
72
|
+
/** Color for mask-icon */
|
|
73
|
+
color?: string
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Standard `<script>` tag attributes. */
|
|
77
|
+
export interface ScriptTag {
|
|
78
|
+
/** External script URL */
|
|
79
|
+
src?: string
|
|
80
|
+
/** Script MIME type or module type (e.g. "module", "importmap") */
|
|
81
|
+
type?: string
|
|
82
|
+
/** Load asynchronously */
|
|
83
|
+
async?: string
|
|
84
|
+
/** Defer execution until document is parsed */
|
|
85
|
+
defer?: string
|
|
86
|
+
/** CORS mode */
|
|
87
|
+
crossorigin?: string
|
|
88
|
+
/** Subresource integrity hash */
|
|
89
|
+
integrity?: string
|
|
90
|
+
/** Exclude from module-supporting browsers */
|
|
91
|
+
nomodule?: string
|
|
92
|
+
/** Referrer policy */
|
|
93
|
+
referrerpolicy?: string
|
|
94
|
+
/** Fetch priority hint */
|
|
95
|
+
fetchpriority?: string
|
|
96
|
+
/** Inline script content */
|
|
97
|
+
children?: string
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Standard `<style>` tag attributes. */
|
|
101
|
+
export interface StyleTag {
|
|
102
|
+
/** Inline CSS content (required) */
|
|
103
|
+
children: string
|
|
104
|
+
/** Media query for conditional styles */
|
|
105
|
+
media?: string
|
|
106
|
+
/** Nonce for CSP */
|
|
107
|
+
nonce?: string
|
|
108
|
+
/** Title for alternate stylesheets */
|
|
109
|
+
title?: string
|
|
110
|
+
/** Render-blocking behavior */
|
|
111
|
+
blocking?: string
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Standard `<base>` tag attributes. */
|
|
115
|
+
export interface BaseTag {
|
|
116
|
+
/** Base URL for relative URLs in the document */
|
|
117
|
+
href?: string
|
|
118
|
+
/** Default target for links and forms */
|
|
119
|
+
target?: "_blank" | "_self" | "_parent" | "_top"
|
|
120
|
+
}
|
|
121
|
+
|
|
20
122
|
export interface UseHeadInput {
|
|
21
123
|
title?: string
|
|
22
124
|
/**
|
|
@@ -25,14 +127,14 @@ export interface UseHeadInput {
|
|
|
25
127
|
* @example useHead({ titleTemplate: "%s | My App" })
|
|
26
128
|
*/
|
|
27
129
|
titleTemplate?: string | ((title: string) => string)
|
|
28
|
-
meta?:
|
|
29
|
-
link?:
|
|
30
|
-
script?:
|
|
31
|
-
style?:
|
|
130
|
+
meta?: MetaTag[]
|
|
131
|
+
link?: LinkTag[]
|
|
132
|
+
script?: ScriptTag[]
|
|
133
|
+
style?: StyleTag[]
|
|
32
134
|
noscript?: { children: string }[]
|
|
33
135
|
/** Convenience: emits a <script type="application/ld+json"> tag with JSON.stringify'd content */
|
|
34
136
|
jsonLd?: Record<string, unknown> | Record<string, unknown>[]
|
|
35
|
-
base?:
|
|
137
|
+
base?: BaseTag
|
|
36
138
|
/** Attributes to set on the <html> element (e.g. { lang: "en", dir: "ltr" }) */
|
|
37
139
|
htmlAttrs?: Record<string, string>
|
|
38
140
|
/** Attributes to set on the <body> element (e.g. { class: "dark" }) */
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
export type {
|
|
1
|
+
export type {
|
|
2
|
+
BaseTag,
|
|
3
|
+
HeadContextValue,
|
|
4
|
+
HeadEntry,
|
|
5
|
+
HeadTag,
|
|
6
|
+
LinkTag,
|
|
7
|
+
MetaTag,
|
|
8
|
+
ScriptTag,
|
|
9
|
+
StyleTag,
|
|
10
|
+
UseHeadInput,
|
|
11
|
+
} from "./context"
|
|
2
12
|
export { createHeadContext, HeadContext } from "./context"
|
|
3
13
|
export type { HeadProviderProps } from "./provider"
|
|
4
14
|
export { HeadProvider } from "./provider"
|
package/src/use-head.ts
CHANGED
|
@@ -4,6 +4,15 @@ import type { HeadEntry, HeadTag, UseHeadInput } from "./context"
|
|
|
4
4
|
import { HeadContext } from "./context"
|
|
5
5
|
import { syncDom } from "./dom"
|
|
6
6
|
|
|
7
|
+
/** Cast a strict tag interface to the internal props format, stripping undefined values */
|
|
8
|
+
function toProps(obj: Record<string, string | undefined>): Record<string, string> {
|
|
9
|
+
const result: Record<string, string> = {}
|
|
10
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
11
|
+
if (v !== undefined) result[k] = v
|
|
12
|
+
}
|
|
13
|
+
return result
|
|
14
|
+
}
|
|
15
|
+
|
|
7
16
|
function buildEntry(o: UseHeadInput): HeadEntry {
|
|
8
17
|
const tags: HeadTag[] = []
|
|
9
18
|
if (o.title != null) tags.push({ tag: "title", key: "title", children: o.title })
|
|
@@ -11,14 +20,14 @@ function buildEntry(o: UseHeadInput): HeadEntry {
|
|
|
11
20
|
tags.push({
|
|
12
21
|
tag: "meta",
|
|
13
22
|
key: m.name ?? m.property ?? `meta-${i}`,
|
|
14
|
-
props: m,
|
|
23
|
+
props: toProps(m as Record<string, string | undefined>),
|
|
15
24
|
})
|
|
16
25
|
})
|
|
17
26
|
o.link?.forEach((l, i) => {
|
|
18
27
|
tags.push({
|
|
19
28
|
tag: "link",
|
|
20
29
|
key: l.href ? `link-${l.rel || ""}-${l.href}` : l.rel ? `link-${l.rel}` : `link-${i}`,
|
|
21
|
-
props: l,
|
|
30
|
+
props: toProps(l as Record<string, string | undefined>),
|
|
22
31
|
})
|
|
23
32
|
})
|
|
24
33
|
o.script?.forEach((s, i) => {
|
|
@@ -26,7 +35,7 @@ function buildEntry(o: UseHeadInput): HeadEntry {
|
|
|
26
35
|
tags.push({
|
|
27
36
|
tag: "script",
|
|
28
37
|
key: s.src ?? `script-${i}`,
|
|
29
|
-
props: rest as Record<string, string
|
|
38
|
+
props: toProps(rest as Record<string, string | undefined>),
|
|
30
39
|
...(children != null ? { children } : {}),
|
|
31
40
|
})
|
|
32
41
|
})
|
|
@@ -35,7 +44,7 @@ function buildEntry(o: UseHeadInput): HeadEntry {
|
|
|
35
44
|
tags.push({
|
|
36
45
|
tag: "style",
|
|
37
46
|
key: `style-${i}`,
|
|
38
|
-
props: rest as Record<string, string
|
|
47
|
+
props: toProps(rest as Record<string, string | undefined>),
|
|
39
48
|
children,
|
|
40
49
|
})
|
|
41
50
|
})
|
|
@@ -50,7 +59,12 @@ function buildEntry(o: UseHeadInput): HeadEntry {
|
|
|
50
59
|
children: JSON.stringify(o.jsonLd),
|
|
51
60
|
})
|
|
52
61
|
}
|
|
53
|
-
if (o.base)
|
|
62
|
+
if (o.base)
|
|
63
|
+
tags.push({
|
|
64
|
+
tag: "base",
|
|
65
|
+
key: "base",
|
|
66
|
+
props: toProps(o.base as Record<string, string | undefined>),
|
|
67
|
+
})
|
|
54
68
|
return {
|
|
55
69
|
tags,
|
|
56
70
|
titleTemplate: o.titleTemplate,
|