@pyreon/head 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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":"b9b401cf-1","name":"context.ts"},{"uid":"b9b401cf-3","name":"provider.tsx"},{"uid":"b9b401cf-5","name":"ssr.ts"},{"uid":"b9b401cf-7","name":"dom.ts"},{"uid":"b9b401cf-9","name":"use-head.ts"},{"uid":"b9b401cf-11","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"b9b401cf-1":{"renderedLength":1012,"gzipLength":400,"brotliLength":0,"metaUid":"b9b401cf-0"},"b9b401cf-3":{"renderedLength":724,"gzipLength":430,"brotliLength":0,"metaUid":"b9b401cf-2"},"b9b401cf-5":{"renderedLength":1297,"gzipLength":642,"brotliLength":0,"metaUid":"b9b401cf-4"},"b9b401cf-7":{"renderedLength":2832,"gzipLength":1120,"brotliLength":0,"metaUid":"b9b401cf-6"},"b9b401cf-9":{"renderedLength":2187,"gzipLength":934,"brotliLength":0,"metaUid":"b9b401cf-8"},"b9b401cf-11":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"b9b401cf-10"}},"nodeMetas":{"b9b401cf-0":{"id":"/src/context.ts","moduleParts":{"index.js":"b9b401cf-1"},"imported":[{"uid":"b9b401cf-12"}],"importedBy":[{"uid":"b9b401cf-10"},{"uid":"b9b401cf-2"},{"uid":"b9b401cf-4"},{"uid":"b9b401cf-8"}]},"b9b401cf-2":{"id":"/src/provider.tsx","moduleParts":{"index.js":"b9b401cf-3"},"imported":[{"uid":"b9b401cf-12"},{"uid":"b9b401cf-0"}],"importedBy":[{"uid":"b9b401cf-10"}]},"b9b401cf-4":{"id":"/src/ssr.ts","moduleParts":{"index.js":"b9b401cf-5"},"imported":[{"uid":"b9b401cf-12"},{"uid":"b9b401cf-13"},{"uid":"b9b401cf-0"}],"importedBy":[{"uid":"b9b401cf-10"}]},"b9b401cf-6":{"id":"/src/dom.ts","moduleParts":{"index.js":"b9b401cf-7"},"imported":[],"importedBy":[{"uid":"b9b401cf-8"}]},"b9b401cf-8":{"id":"/src/use-head.ts","moduleParts":{"index.js":"b9b401cf-9"},"imported":[{"uid":"b9b401cf-12"},{"uid":"b9b401cf-14"},{"uid":"b9b401cf-0"},{"uid":"b9b401cf-6"}],"importedBy":[{"uid":"b9b401cf-10"}]},"b9b401cf-10":{"id":"/src/index.ts","moduleParts":{"index.js":"b9b401cf-11"},"imported":[{"uid":"b9b401cf-0"},{"uid":"b9b401cf-2"},{"uid":"b9b401cf-4"},{"uid":"b9b401cf-8"}],"importedBy":[],"isEntry":true},"b9b401cf-12":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"b9b401cf-0"},{"uid":"b9b401cf-2"},{"uid":"b9b401cf-4"},{"uid":"b9b401cf-8"}]},"b9b401cf-13":{"id":"@pyreon/runtime-server","moduleParts":{},"imported":[],"importedBy":[{"uid":"b9b401cf-4"}]},"b9b401cf-14":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"b9b401cf-8"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5389
+ const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"114b47fc-1","name":"context.ts"},{"uid":"114b47fc-3","name":"provider.tsx"},{"uid":"114b47fc-5","name":"ssr.ts"},{"uid":"114b47fc-7","name":"dom.ts"},{"uid":"114b47fc-9","name":"use-head.ts"},{"uid":"114b47fc-11","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"114b47fc-1":{"renderedLength":1373,"gzipLength":509,"brotliLength":0,"metaUid":"114b47fc-0"},"114b47fc-3":{"renderedLength":724,"gzipLength":430,"brotliLength":0,"metaUid":"114b47fc-2"},"114b47fc-5":{"renderedLength":1370,"gzipLength":701,"brotliLength":0,"metaUid":"114b47fc-4"},"114b47fc-7":{"renderedLength":3401,"gzipLength":1293,"brotliLength":0,"metaUid":"114b47fc-6"},"114b47fc-9":{"renderedLength":2187,"gzipLength":934,"brotliLength":0,"metaUid":"114b47fc-8"},"114b47fc-11":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"114b47fc-10"}},"nodeMetas":{"114b47fc-0":{"id":"/src/context.ts","moduleParts":{"index.js":"114b47fc-1"},"imported":[{"uid":"114b47fc-12"}],"importedBy":[{"uid":"114b47fc-10"},{"uid":"114b47fc-2"},{"uid":"114b47fc-4"},{"uid":"114b47fc-8"}]},"114b47fc-2":{"id":"/src/provider.tsx","moduleParts":{"index.js":"114b47fc-3"},"imported":[{"uid":"114b47fc-12"},{"uid":"114b47fc-0"}],"importedBy":[{"uid":"114b47fc-10"}]},"114b47fc-4":{"id":"/src/ssr.ts","moduleParts":{"index.js":"114b47fc-5"},"imported":[{"uid":"114b47fc-12"},{"uid":"114b47fc-13"},{"uid":"114b47fc-0"}],"importedBy":[{"uid":"114b47fc-10"}]},"114b47fc-6":{"id":"/src/dom.ts","moduleParts":{"index.js":"114b47fc-7"},"imported":[],"importedBy":[{"uid":"114b47fc-8"}]},"114b47fc-8":{"id":"/src/use-head.ts","moduleParts":{"index.js":"114b47fc-9"},"imported":[{"uid":"114b47fc-12"},{"uid":"114b47fc-14"},{"uid":"114b47fc-0"},{"uid":"114b47fc-6"}],"importedBy":[{"uid":"114b47fc-10"}]},"114b47fc-10":{"id":"/src/index.ts","moduleParts":{"index.js":"114b47fc-11"},"imported":[{"uid":"114b47fc-0"},{"uid":"114b47fc-2"},{"uid":"114b47fc-4"},{"uid":"114b47fc-8"}],"importedBy":[],"isEntry":true},"114b47fc-12":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"114b47fc-0"},{"uid":"114b47fc-2"},{"uid":"114b47fc-4"},{"uid":"114b47fc-8"}]},"114b47fc-13":{"id":"@pyreon/runtime-server","moduleParts":{},"imported":[],"importedBy":[{"uid":"114b47fc-4"}]},"114b47fc-14":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"114b47fc-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;
package/lib/index.js CHANGED
@@ -5,34 +5,55 @@ import { effect } from "@pyreon/reactivity";
5
5
  //#region src/context.ts
6
6
  function createHeadContext() {
7
7
  const map = /* @__PURE__ */ new Map();
8
+ let dirty = true;
9
+ let cachedTags = [];
10
+ let cachedTitleTemplate;
11
+ let cachedHtmlAttrs = {};
12
+ let cachedBodyAttrs = {};
13
+ function rebuild() {
14
+ if (!dirty) return;
15
+ dirty = false;
16
+ const keyed = /* @__PURE__ */ new Map();
17
+ const unkeyed = [];
18
+ let titleTemplate;
19
+ const htmlAttrs = {};
20
+ const bodyAttrs = {};
21
+ for (const entry of map.values()) {
22
+ for (const tag of entry.tags) if (tag.key) keyed.set(tag.key, tag);
23
+ else unkeyed.push(tag);
24
+ if (entry.titleTemplate !== void 0) titleTemplate = entry.titleTemplate;
25
+ if (entry.htmlAttrs) Object.assign(htmlAttrs, entry.htmlAttrs);
26
+ if (entry.bodyAttrs) Object.assign(bodyAttrs, entry.bodyAttrs);
27
+ }
28
+ cachedTags = [...keyed.values(), ...unkeyed];
29
+ cachedTitleTemplate = titleTemplate;
30
+ cachedHtmlAttrs = htmlAttrs;
31
+ cachedBodyAttrs = bodyAttrs;
32
+ }
8
33
  return {
9
34
  add(id, entry) {
10
35
  map.set(id, entry);
36
+ dirty = true;
11
37
  },
12
38
  remove(id) {
13
39
  map.delete(id);
40
+ dirty = true;
14
41
  },
15
42
  resolve() {
16
- const keyed = /* @__PURE__ */ new Map();
17
- const unkeyed = [];
18
- for (const entry of map.values()) for (const tag of entry.tags) if (tag.key) keyed.set(tag.key, tag);
19
- else unkeyed.push(tag);
20
- return [...keyed.values(), ...unkeyed];
43
+ rebuild();
44
+ return cachedTags;
21
45
  },
22
46
  resolveTitleTemplate() {
23
- let template;
24
- for (const entry of map.values()) if (entry.titleTemplate !== void 0) template = entry.titleTemplate;
25
- return template;
47
+ rebuild();
48
+ return cachedTitleTemplate;
26
49
  },
27
50
  resolveHtmlAttrs() {
28
- const attrs = {};
29
- for (const entry of map.values()) if (entry.htmlAttrs) Object.assign(attrs, entry.htmlAttrs);
30
- return attrs;
51
+ rebuild();
52
+ return cachedHtmlAttrs;
31
53
  },
32
54
  resolveBodyAttrs() {
33
- const attrs = {};
34
- for (const entry of map.values()) if (entry.bodyAttrs) Object.assign(attrs, entry.bodyAttrs);
35
- return attrs;
55
+ rebuild();
56
+ return cachedBodyAttrs;
36
57
  }
37
58
  };
38
59
  }
@@ -95,13 +116,22 @@ function serializeTag(tag, titleTemplate) {
95
116
  if (VOID_TAGS.has(tag.tag)) return `${open} />`;
96
117
  return `${open}>${(tag.children || "").replace(/<\/(script|style|noscript)/gi, "<\\/$1").replace(/<!--/g, "<\\!--")}</${tag.tag}>`;
97
118
  }
119
+ const ESC_RE = /[&<>"]/g;
120
+ const ESC_MAP = {
121
+ "&": "&amp;",
122
+ "<": "&lt;",
123
+ ">": "&gt;",
124
+ "\"": "&quot;"
125
+ };
98
126
  function esc(s) {
99
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
127
+ return ESC_RE.test(s) ? s.replace(ESC_RE, (ch) => ESC_MAP[ch]) : s;
100
128
  }
101
129
 
102
130
  //#endregion
103
131
  //#region src/dom.ts
104
132
  const ATTR = "data-pyreon-head";
133
+ /** Tracks managed elements by key — avoids querySelectorAll on every sync */
134
+ const managedElements = /* @__PURE__ */ new Map();
105
135
  /**
106
136
  * Sync the resolved head tags to the real DOM <head>.
107
137
  * Uses incremental diffing: matches existing elements by key, patches attributes
@@ -110,25 +140,36 @@ const ATTR = "data-pyreon-head";
110
140
  * No-op on the server (typeof document === "undefined").
111
141
  */
112
142
  function patchExistingTag(found, tag, kept) {
113
- kept.add(found);
143
+ kept.add(found.getAttribute(ATTR));
114
144
  patchAttrs(found, tag.props);
115
145
  const content = String(tag.children);
116
146
  if (found.textContent !== content) found.textContent = content;
117
147
  }
118
148
  function createNewTag(tag) {
119
149
  const el = document.createElement(tag.tag);
120
- el.setAttribute(ATTR, tag.key);
150
+ const key = tag.key;
151
+ el.setAttribute(ATTR, key);
121
152
  for (const [k, v] of Object.entries(tag.props)) el.setAttribute(k, v);
122
153
  if (tag.children) el.textContent = tag.children;
123
154
  document.head.appendChild(el);
155
+ managedElements.set(key, el);
124
156
  }
125
157
  function syncDom(ctx) {
126
158
  if (typeof document === "undefined") return;
127
159
  const tags = ctx.resolve();
128
160
  const titleTemplate = ctx.resolveTitleTemplate();
129
- const existing = document.head.querySelectorAll(`[${ATTR}]`);
130
- const byKey = /* @__PURE__ */ new Map();
131
- for (const el of existing) byKey.set(el.getAttribute(ATTR), el);
161
+ let needsSeed = managedElements.size === 0;
162
+ if (!needsSeed) {
163
+ const sample = managedElements.values().next().value;
164
+ if (sample && !sample.isConnected) {
165
+ managedElements.clear();
166
+ needsSeed = true;
167
+ }
168
+ }
169
+ if (needsSeed) {
170
+ const existing = document.head.querySelectorAll(`[${ATTR}]`);
171
+ for (const el of existing) managedElements.set(el.getAttribute(ATTR), el);
172
+ }
132
173
  const kept = /* @__PURE__ */ new Set();
133
174
  for (const tag of tags) {
134
175
  if (tag.tag === "title") {
@@ -136,11 +177,21 @@ function syncDom(ctx) {
136
177
  continue;
137
178
  }
138
179
  const key = tag.key;
139
- const found = byKey.get(key);
180
+ const found = managedElements.get(key);
140
181
  if (found && found.tagName.toLowerCase() === tag.tag) patchExistingTag(found, tag, kept);
141
- else createNewTag(tag);
182
+ else {
183
+ if (found) {
184
+ found.remove();
185
+ managedElements.delete(key);
186
+ }
187
+ createNewTag(tag);
188
+ kept.add(key);
189
+ }
190
+ }
191
+ for (const [key, el] of managedElements) if (!kept.has(key)) {
192
+ el.remove();
193
+ managedElements.delete(key);
142
194
  }
143
- for (const el of existing) if (!kept.has(el)) el.remove();
144
195
  syncElementAttrs(document.documentElement, ctx.resolveHtmlAttrs());
145
196
  syncElementAttrs(document.body, ctx.resolveBodyAttrs());
146
197
  }
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/context.ts","../src/provider.tsx","../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 return {\n add(id, entry) {\n map.set(id, entry)\n },\n remove(id) {\n map.delete(id)\n },\n resolve() {\n const keyed = new Map<string, HeadTag>()\n const unkeyed: HeadTag[] = []\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 }\n return [...keyed.values(), ...unkeyed]\n },\n resolveTitleTemplate() {\n let template: (string | ((title: string) => string)) | undefined\n for (const entry of map.values()) {\n if (entry.titleTemplate !== undefined) template = entry.titleTemplate\n }\n return template\n },\n resolveHtmlAttrs() {\n const attrs: Record<string, string> = {}\n for (const entry of map.values()) {\n if (entry.htmlAttrs) Object.assign(attrs, entry.htmlAttrs)\n }\n return attrs\n },\n resolveBodyAttrs() {\n const attrs: Record<string, string> = {}\n for (const entry of map.values()) {\n if (entry.bodyAttrs) Object.assign(attrs, entry.bodyAttrs)\n }\n return attrs\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\nfunction esc(s: string): string {\n return s\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n}\n","import type { HeadContextValue } from \"./context\"\n\nconst ATTR = \"data-pyreon-head\"\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<Element>,\n): void {\n kept.add(found)\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 el.setAttribute(ATTR, tag.key as string)\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}\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 const existing = document.head.querySelectorAll(`[${ATTR}]`)\n const byKey = new Map<string, Element>()\n for (const el of existing) {\n byKey.set(el.getAttribute(ATTR) as string, el)\n }\n\n const kept = new Set<Element>()\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 = byKey.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 createNewTag(\n tag as { tag: string; props: Record<string, unknown>; children: string; key: unknown },\n )\n }\n }\n\n for (const el of existing) {\n if (!kept.has(el)) el.remove()\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":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/context.ts","../src/provider.tsx","../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> = { \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\" }\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"}
@@ -5,33 +5,54 @@ import { effect } from "@pyreon/reactivity";
5
5
  //#region src/context.ts
6
6
  function createHeadContext() {
7
7
  const map = /* @__PURE__ */new Map();
8
+ let dirty = true;
9
+ let cachedTags = [];
10
+ let cachedTitleTemplate;
11
+ let cachedHtmlAttrs = {};
12
+ let cachedBodyAttrs = {};
13
+ function rebuild() {
14
+ if (!dirty) return;
15
+ dirty = false;
16
+ const keyed = /* @__PURE__ */new Map();
17
+ const unkeyed = [];
18
+ let titleTemplate;
19
+ const htmlAttrs = {};
20
+ const bodyAttrs = {};
21
+ for (const entry of map.values()) {
22
+ for (const tag of entry.tags) if (tag.key) keyed.set(tag.key, tag);else unkeyed.push(tag);
23
+ if (entry.titleTemplate !== void 0) titleTemplate = entry.titleTemplate;
24
+ if (entry.htmlAttrs) Object.assign(htmlAttrs, entry.htmlAttrs);
25
+ if (entry.bodyAttrs) Object.assign(bodyAttrs, entry.bodyAttrs);
26
+ }
27
+ cachedTags = [...keyed.values(), ...unkeyed];
28
+ cachedTitleTemplate = titleTemplate;
29
+ cachedHtmlAttrs = htmlAttrs;
30
+ cachedBodyAttrs = bodyAttrs;
31
+ }
8
32
  return {
9
33
  add(id, entry) {
10
34
  map.set(id, entry);
35
+ dirty = true;
11
36
  },
12
37
  remove(id) {
13
38
  map.delete(id);
39
+ dirty = true;
14
40
  },
15
41
  resolve() {
16
- const keyed = /* @__PURE__ */new Map();
17
- const unkeyed = [];
18
- for (const entry of map.values()) for (const tag of entry.tags) if (tag.key) keyed.set(tag.key, tag);else unkeyed.push(tag);
19
- return [...keyed.values(), ...unkeyed];
42
+ rebuild();
43
+ return cachedTags;
20
44
  },
21
45
  resolveTitleTemplate() {
22
- let template;
23
- for (const entry of map.values()) if (entry.titleTemplate !== void 0) template = entry.titleTemplate;
24
- return template;
46
+ rebuild();
47
+ return cachedTitleTemplate;
25
48
  },
26
49
  resolveHtmlAttrs() {
27
- const attrs = {};
28
- for (const entry of map.values()) if (entry.htmlAttrs) Object.assign(attrs, entry.htmlAttrs);
29
- return attrs;
50
+ rebuild();
51
+ return cachedHtmlAttrs;
30
52
  },
31
53
  resolveBodyAttrs() {
32
- const attrs = {};
33
- for (const entry of map.values()) if (entry.bodyAttrs) Object.assign(attrs, entry.bodyAttrs);
34
- return attrs;
54
+ rebuild();
55
+ return cachedBodyAttrs;
35
56
  }
36
57
  };
37
58
  }
@@ -62,7 +83,7 @@ function serializeTag(tag, titleTemplate) {
62
83
  return `${open}>${(tag.children || "").replace(/<\/(script|style|noscript)/gi, "<\\/$1").replace(/<!--/g, "<\\!--")}</${tag.tag}>`;
63
84
  }
64
85
  function esc(s) {
65
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
86
+ return ESC_RE.test(s) ? s.replace(ESC_RE, ch => ESC_MAP[ch]) : s;
66
87
  }
67
88
 
68
89
  //#endregion
@@ -76,25 +97,36 @@ function esc(s) {
76
97
  * No-op on the server (typeof document === "undefined").
77
98
  */
78
99
  function patchExistingTag(found, tag, kept) {
79
- kept.add(found);
100
+ kept.add(found.getAttribute(ATTR));
80
101
  patchAttrs(found, tag.props);
81
102
  const content = String(tag.children);
82
103
  if (found.textContent !== content) found.textContent = content;
83
104
  }
84
105
  function createNewTag(tag) {
85
106
  const el = document.createElement(tag.tag);
86
- el.setAttribute(ATTR, tag.key);
107
+ const key = tag.key;
108
+ el.setAttribute(ATTR, key);
87
109
  for (const [k, v] of Object.entries(tag.props)) el.setAttribute(k, v);
88
110
  if (tag.children) el.textContent = tag.children;
89
111
  document.head.appendChild(el);
112
+ managedElements.set(key, el);
90
113
  }
91
114
  function syncDom(ctx) {
92
115
  if (typeof document === "undefined") return;
93
116
  const tags = ctx.resolve();
94
117
  const titleTemplate = ctx.resolveTitleTemplate();
95
- const existing = document.head.querySelectorAll(`[${ATTR}]`);
96
- const byKey = /* @__PURE__ */new Map();
97
- for (const el of existing) byKey.set(el.getAttribute(ATTR), el);
118
+ let needsSeed = managedElements.size === 0;
119
+ if (!needsSeed) {
120
+ const sample = managedElements.values().next().value;
121
+ if (sample && !sample.isConnected) {
122
+ managedElements.clear();
123
+ needsSeed = true;
124
+ }
125
+ }
126
+ if (needsSeed) {
127
+ const existing = document.head.querySelectorAll(`[${ATTR}]`);
128
+ for (const el of existing) managedElements.set(el.getAttribute(ATTR), el);
129
+ }
98
130
  const kept = /* @__PURE__ */new Set();
99
131
  for (const tag of tags) {
100
132
  if (tag.tag === "title") {
@@ -102,10 +134,20 @@ function syncDom(ctx) {
102
134
  continue;
103
135
  }
104
136
  const key = tag.key;
105
- const found = byKey.get(key);
106
- if (found && found.tagName.toLowerCase() === tag.tag) patchExistingTag(found, tag, kept);else createNewTag(tag);
137
+ const found = managedElements.get(key);
138
+ if (found && found.tagName.toLowerCase() === tag.tag) patchExistingTag(found, tag, kept);else {
139
+ if (found) {
140
+ found.remove();
141
+ managedElements.delete(key);
142
+ }
143
+ createNewTag(tag);
144
+ kept.add(key);
145
+ }
146
+ }
147
+ for (const [key, el] of managedElements) if (!kept.has(key)) {
148
+ el.remove();
149
+ managedElements.delete(key);
107
150
  }
108
- for (const el of existing) if (!kept.has(el)) el.remove();
109
151
  syncElementAttrs(document.documentElement, ctx.resolveHtmlAttrs());
110
152
  syncElementAttrs(document.body, ctx.resolveBodyAttrs());
111
153
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/context.ts","../../src/provider.tsx","../../src/ssr.ts","../../src/dom.ts","../../src/use-head.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/context.ts","../../src/provider.tsx","../../src/ssr.ts","../../src/dom.ts","../../src/use-head.ts"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index2.d.ts","names":[],"sources":["../../src/context.ts","../../src/provider.tsx","../../src/ssr.ts","../../src/use-head.ts"],"mappings":";;;;UAIiB,OAAA;;EAEf,GAAA;;AAFF;;;;EAQE,GAAA;EAAA;EAEA,KAAA,GAAQ,MAAA;EAAA;EAER,QAAA;AAAA;AAAA,UAGe,YAAA;EACf,KAAA;EAD2B;;;;;EAO3B,aAAA,cAA2B,KAAA;EAC3B,IAAA,GAAO,MAAA;EACP,IAAA,GAAO,MAAA;EACP,MAAA;IAAY,GAAA;IAAc,QAAA;EAAA,IAAsB,MAAA;EAChD,KAAA;IAAW,QAAA;EAAA,IAAqB,MAAA;EAChC,QAAA;IAAa,QAAA;EAAA;EAHb;EAKA,MAAA,GAAS,MAAA,oBAA0B,MAAA;EACnC,IAAA,GAAO,MAAA;EALK;EAOZ,SAAA,GAAY,MAAA;EAPoC;EAShD,SAAA,GAAY,MAAA;AAAA;AAAA,UAKG,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;EAhBK;EAkBZ,OAAA,IAAW,OAAA;EAhBC;EAkBZ,oBAAA,gBAAoC,KAAA;EAlBlB;EAoBlB,gBAAA,IAAoB,MAAA;EAfI;EAiBxB,gBAAA,IAAoB,MAAA;AAAA;AAAA,iBAGN,iBAAA,CAAA,GAAqB,gBAAA;AAAA,cA4CxB,WAAA,EAAW,aAAA,CAAA,OAAA,CAAA,gBAAA;;;UCtGP,iBAAA,SAA0B,KAAA;EACzC,OAAA,GAAU,gBAAA;EACV,QAAA,GAAW,UAAA;AAAA;;;;;;;;;;ADYb;;;;;cCKa,YAAA,EAAc,WAAA,CAAY,iBAAA;;;;;;ADpBvC;;;;;;;;;;;AAeA;;UEKiB,oBAAA;EACf,IAAA;EACA,IAAA;EFGgD;EEDhD,SAAA,EAAW,MAAA;EFKF;EEHT,SAAA,EAAW,MAAA;AAAA;AAAA,iBAGS,cAAA,CAAe,GAAA,EAAK,KAAA,GAAQ,OAAA,CAAQ,oBAAA;;;;;;AF7B1D;;;;;;;;iBGoEgB,OAAA,CAAQ,KAAA,EAAO,YAAA,UAAsB,YAAA"}
1
+ {"version":3,"file":"index2.d.ts","names":[],"sources":["../../src/context.ts","../../src/provider.tsx","../../src/ssr.ts","../../src/use-head.ts"],"mappings":";;;;UAIiB,OAAA;;EAEf,GAAA;;AAFF;;;;EAQE,GAAA;EAAA;EAEA,KAAA,GAAQ,MAAA;EAAA;EAER,QAAA;AAAA;AAAA,UAGe,YAAA;EACf,KAAA;EAD2B;;;;;EAO3B,aAAA,cAA2B,KAAA;EAC3B,IAAA,GAAO,MAAA;EACP,IAAA,GAAO,MAAA;EACP,MAAA;IAAY,GAAA;IAAc,QAAA;EAAA,IAAsB,MAAA;EAChD,KAAA;IAAW,QAAA;EAAA,IAAqB,MAAA;EAChC,QAAA;IAAa,QAAA;EAAA;EAHb;EAKA,MAAA,GAAS,MAAA,oBAA0B,MAAA;EACnC,IAAA,GAAO,MAAA;EALK;EAOZ,SAAA,GAAY,MAAA;EAPoC;EAShD,SAAA,GAAY,MAAA;AAAA;AAAA,UAKG,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;EAhBK;EAkBZ,OAAA,IAAW,OAAA;EAhBC;EAkBZ,oBAAA,gBAAoC,KAAA;EAlBlB;EAoBlB,gBAAA,IAAoB,MAAA;EAfI;EAiBxB,gBAAA,IAAoB,MAAA;AAAA;AAAA,iBAGN,iBAAA,CAAA,GAAqB,gBAAA;AAAA,cAgExB,WAAA,EAAW,aAAA,CAAA,OAAA,CAAA,gBAAA;;;UC1HP,iBAAA,SAA0B,KAAA;EACzC,OAAA,GAAU,gBAAA;EACV,QAAA,GAAW,UAAA;AAAA;;;;;;;;;;ADYb;;;;;cCKa,YAAA,EAAc,WAAA,CAAY,iBAAA;;;;;;ADpBvC;;;;;;;;;;;AAeA;;UEKiB,oBAAA;EACf,IAAA;EACA,IAAA;EFGgD;EEDhD,SAAA,EAAW,MAAA;EFKF;EEHT,SAAA,EAAW,MAAA;AAAA;AAAA,iBAGS,cAAA,CAAe,GAAA,EAAK,KAAA,GAAQ,OAAA,CAAQ,oBAAA;;;;;;AF7B1D;;;;;;;;iBGoEgB,OAAA,CAAQ,KAAA,EAAO,YAAA,UAAsB,YAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/head",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "description": "Head tag management for Pyreon — works in SSR and CSR",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -54,9 +54,9 @@
54
54
  "prepublishOnly": "bun run build"
55
55
  },
56
56
  "dependencies": {
57
- "@pyreon/core": "^0.3.1",
58
- "@pyreon/reactivity": "^0.3.1",
59
- "@pyreon/runtime-server": "^0.3.1"
57
+ "@pyreon/core": "^0.5.0",
58
+ "@pyreon/reactivity": "^0.5.0",
59
+ "@pyreon/runtime-server": "^0.5.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@happy-dom/global-registrator": "*",
package/src/context.ts CHANGED
@@ -63,44 +63,64 @@ export interface HeadContextValue {
63
63
 
64
64
  export function createHeadContext(): HeadContextValue {
65
65
  const map = new Map<symbol, HeadEntry>()
66
+
67
+ // ── Cached resolve ───────────────────────────────────────────────────────
68
+ let dirty = true
69
+ let cachedTags: HeadTag[] = []
70
+ let cachedTitleTemplate: (string | ((title: string) => string)) | undefined
71
+ let cachedHtmlAttrs: Record<string, string> = {}
72
+ let cachedBodyAttrs: Record<string, string> = {}
73
+
74
+ function rebuild(): void {
75
+ if (!dirty) return
76
+ dirty = false
77
+
78
+ const keyed = new Map<string, HeadTag>()
79
+ const unkeyed: HeadTag[] = []
80
+ let titleTemplate: (string | ((title: string) => string)) | undefined
81
+ const htmlAttrs: Record<string, string> = {}
82
+ const bodyAttrs: Record<string, string> = {}
83
+
84
+ for (const entry of map.values()) {
85
+ for (const tag of entry.tags) {
86
+ if (tag.key) keyed.set(tag.key, tag)
87
+ else unkeyed.push(tag)
88
+ }
89
+ if (entry.titleTemplate !== undefined) titleTemplate = entry.titleTemplate
90
+ if (entry.htmlAttrs) Object.assign(htmlAttrs, entry.htmlAttrs)
91
+ if (entry.bodyAttrs) Object.assign(bodyAttrs, entry.bodyAttrs)
92
+ }
93
+
94
+ cachedTags = [...keyed.values(), ...unkeyed]
95
+ cachedTitleTemplate = titleTemplate
96
+ cachedHtmlAttrs = htmlAttrs
97
+ cachedBodyAttrs = bodyAttrs
98
+ }
99
+
66
100
  return {
67
101
  add(id, entry) {
68
102
  map.set(id, entry)
103
+ dirty = true
69
104
  },
70
105
  remove(id) {
71
106
  map.delete(id)
107
+ dirty = true
72
108
  },
73
109
  resolve() {
74
- const keyed = new Map<string, HeadTag>()
75
- const unkeyed: HeadTag[] = []
76
- for (const entry of map.values()) {
77
- for (const tag of entry.tags) {
78
- if (tag.key) keyed.set(tag.key, tag)
79
- else unkeyed.push(tag)
80
- }
81
- }
82
- return [...keyed.values(), ...unkeyed]
110
+ rebuild()
111
+ return cachedTags
83
112
  },
84
113
  resolveTitleTemplate() {
85
- let template: (string | ((title: string) => string)) | undefined
86
- for (const entry of map.values()) {
87
- if (entry.titleTemplate !== undefined) template = entry.titleTemplate
88
- }
89
- return template
114
+ rebuild()
115
+ return cachedTitleTemplate
90
116
  },
91
117
  resolveHtmlAttrs() {
92
- const attrs: Record<string, string> = {}
93
- for (const entry of map.values()) {
94
- if (entry.htmlAttrs) Object.assign(attrs, entry.htmlAttrs)
95
- }
96
- return attrs
118
+ rebuild()
119
+ return cachedHtmlAttrs
97
120
  },
98
121
  resolveBodyAttrs() {
99
- const attrs: Record<string, string> = {}
100
- for (const entry of map.values()) {
101
- if (entry.bodyAttrs) Object.assign(attrs, entry.bodyAttrs)
102
- }
103
- return attrs
122
+ rebuild()
123
+ return cachedBodyAttrs
104
124
  },
105
125
  }
106
126
  }
package/src/dom.ts CHANGED
@@ -2,6 +2,9 @@ import type { HeadContextValue } from "./context"
2
2
 
3
3
  const ATTR = "data-pyreon-head"
4
4
 
5
+ /** Tracks managed elements by key — avoids querySelectorAll on every sync */
6
+ const managedElements = new Map<string, Element>()
7
+
5
8
  /**
6
9
  * Sync the resolved head tags to the real DOM <head>.
7
10
  * Uses incremental diffing: matches existing elements by key, patches attributes
@@ -12,9 +15,9 @@ const ATTR = "data-pyreon-head"
12
15
  function patchExistingTag(
13
16
  found: Element,
14
17
  tag: { props: Record<string, unknown>; children: string },
15
- kept: Set<Element>,
18
+ kept: Set<string>,
16
19
  ): void {
17
- kept.add(found)
20
+ kept.add(found.getAttribute(ATTR) as string)
18
21
  patchAttrs(found, tag.props as Record<string, string>)
19
22
  const content = String(tag.children)
20
23
  if (found.textContent !== content) found.textContent = content
@@ -27,12 +30,14 @@ function createNewTag(tag: {
27
30
  key: unknown
28
31
  }): void {
29
32
  const el = document.createElement(tag.tag)
30
- el.setAttribute(ATTR, tag.key as string)
33
+ const key = tag.key as string
34
+ el.setAttribute(ATTR, key)
31
35
  for (const [k, v] of Object.entries(tag.props as Record<string, string>)) {
32
36
  el.setAttribute(k, v)
33
37
  }
34
38
  if (tag.children) el.textContent = tag.children
35
39
  document.head.appendChild(el)
40
+ managedElements.set(key, el)
36
41
  }
37
42
 
38
43
  export function syncDom(ctx: HeadContextValue): void {
@@ -40,13 +45,25 @@ export function syncDom(ctx: HeadContextValue): void {
40
45
 
41
46
  const tags = ctx.resolve()
42
47
  const titleTemplate = ctx.resolveTitleTemplate()
43
- const existing = document.head.querySelectorAll(`[${ATTR}]`)
44
- const byKey = new Map<string, Element>()
45
- for (const el of existing) {
46
- byKey.set(el.getAttribute(ATTR) as string, el)
48
+
49
+ // Seed from DOM on first sync, or re-seed if DOM was reset (e.g. between tests)
50
+ let needsSeed = managedElements.size === 0
51
+ if (!needsSeed) {
52
+ // Check if a tracked element is still in the DOM
53
+ const sample = managedElements.values().next().value
54
+ if (sample && !sample.isConnected) {
55
+ managedElements.clear()
56
+ needsSeed = true
57
+ }
58
+ }
59
+ if (needsSeed) {
60
+ const existing = document.head.querySelectorAll(`[${ATTR}]`)
61
+ for (const el of existing) {
62
+ managedElements.set(el.getAttribute(ATTR) as string, el)
63
+ }
47
64
  }
48
65
 
49
- const kept = new Set<Element>()
66
+ const kept = new Set<string>()
50
67
 
51
68
  for (const tag of tags) {
52
69
  if (tag.tag === "title") {
@@ -55,19 +72,28 @@ export function syncDom(ctx: HeadContextValue): void {
55
72
  }
56
73
 
57
74
  const key = tag.key as string
58
- const found = byKey.get(key)
75
+ const found = managedElements.get(key)
59
76
 
60
77
  if (found && found.tagName.toLowerCase() === tag.tag) {
61
78
  patchExistingTag(found, tag as { props: Record<string, unknown>; children: string }, kept)
62
79
  } else {
80
+ if (found) {
81
+ found.remove()
82
+ managedElements.delete(key)
83
+ }
63
84
  createNewTag(
64
85
  tag as { tag: string; props: Record<string, unknown>; children: string; key: unknown },
65
86
  )
87
+ kept.add(key)
66
88
  }
67
89
  }
68
90
 
69
- for (const el of existing) {
70
- if (!kept.has(el)) el.remove()
91
+ // Remove stale elements
92
+ for (const [key, el] of managedElements) {
93
+ if (!kept.has(key)) {
94
+ el.remove()
95
+ managedElements.delete(key)
96
+ }
71
97
  }
72
98
 
73
99
  syncElementAttrs(document.documentElement, ctx.resolveHtmlAttrs())
package/src/ssr.ts CHANGED
@@ -81,10 +81,9 @@ function serializeTag(tag: HeadTag, titleTemplate?: string | ((title: string) =>
81
81
  return `${open}>${body}</${tag.tag}>`
82
82
  }
83
83
 
84
+ const ESC_RE = /[&<>"]/g
85
+ const ESC_MAP: Record<string, string> = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }
86
+
84
87
  function esc(s: string): string {
85
- return s
86
- .replace(/&/g, "&amp;")
87
- .replace(/</g, "&lt;")
88
- .replace(/>/g, "&gt;")
89
- .replace(/"/g, "&quot;")
88
+ return ESC_RE.test(s) ? s.replace(ESC_RE, (ch) => ESC_MAP[ch] as string) : s
90
89
  }