@lovrozagar/flare 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (387) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +11 -0
  3. package/package.json +146 -0
  4. package/scripts/download-fonts.ts +136 -0
  5. package/scripts/populate-fonts.ts +721 -0
  6. package/src/await.ts +2 -0
  7. package/src/boundaries/index.ts +99 -0
  8. package/src/broadcast/channel.ts +65 -0
  9. package/src/broadcast/context.ts +12 -0
  10. package/src/broadcast/index.ts +75 -0
  11. package/src/broadcast/provider.tsx +7 -0
  12. package/src/build-location.ts +2 -0
  13. package/src/caches/index.ts +384 -0
  14. package/src/client/index.ts +75 -0
  15. package/src/client.tsx +2 -0
  16. package/src/codegen/index.ts +41 -0
  17. package/src/components/await.tsx +140 -0
  18. package/src/components/dev-error-overlay.tsx +154 -0
  19. package/src/components/direction-script.tsx +22 -0
  20. package/src/components/index.ts +150 -0
  21. package/src/components/locale-script.tsx +26 -0
  22. package/src/components/reset-css.tsx +14 -0
  23. package/src/components/ssr-context.tsx +37 -0
  24. package/src/components/theme-script.tsx +22 -0
  25. package/src/components/view-transition-css.tsx +21 -0
  26. package/src/config/index.ts +84 -0
  27. package/src/create-client-only-fn.ts +1 -0
  28. package/src/create-isomorphic-fn.ts +2 -0
  29. package/src/create-layout.ts +1 -0
  30. package/src/create-page.ts +1 -0
  31. package/src/create-path-segment.ts +1 -0
  32. package/src/create-query-client-getter.ts +2 -0
  33. package/src/create-root-layout.ts +1 -0
  34. package/src/create-router.ts +8 -0
  35. package/src/create-server-only-fn.ts +1 -0
  36. package/src/dedupe/index.ts +151 -0
  37. package/src/defer/index.ts +82 -0
  38. package/src/direction/index.tsx +163 -0
  39. package/src/direction.ts +3 -0
  40. package/src/duration/index.ts +18 -0
  41. package/src/env-fn/index.ts +101 -0
  42. package/src/env-fn.ts +2 -0
  43. package/src/errors/index.ts +166 -0
  44. package/src/fonts/abeezee.ts +50 -0
  45. package/src/fonts/abel.ts +26 -0
  46. package/src/fonts/abril-fatface.ts +34 -0
  47. package/src/fonts/acme.ts +26 -0
  48. package/src/fonts/advent-pro.ts +92 -0
  49. package/src/fonts/alegreya-sans.ts +228 -0
  50. package/src/fonts/alegreya.ts +228 -0
  51. package/src/fonts/alex-brush.ts +42 -0
  52. package/src/fonts/alfa-slab-one.ts +42 -0
  53. package/src/fonts/alice.ts +48 -0
  54. package/src/fonts/amatic-sc.ts +94 -0
  55. package/src/fonts/amiri.ts +114 -0
  56. package/src/fonts/antic-slab.ts +26 -0
  57. package/src/fonts/anton.ts +42 -0
  58. package/src/fonts/archivo-black.ts +34 -0
  59. package/src/fonts/archivo-narrow.ts +114 -0
  60. package/src/fonts/archivo.ts +66 -0
  61. package/src/fonts/arimo.ts +256 -0
  62. package/src/fonts/arvo.ts +50 -0
  63. package/src/fonts/asap.ts +66 -0
  64. package/src/fonts/assistant.ts +64 -0
  65. package/src/fonts/baloo-2.ts +82 -0
  66. package/src/fonts/barlow-condensed.ts +114 -0
  67. package/src/fonts/barlow-semi-condensed.ts +114 -0
  68. package/src/fonts/barlow.ts +114 -0
  69. package/src/fonts/be-vietnam-pro.ts +114 -0
  70. package/src/fonts/bebas-neue.ts +34 -0
  71. package/src/fonts/besley.ts +82 -0
  72. package/src/fonts/biorhyme.ts +50 -0
  73. package/src/fonts/bitter.ts +94 -0
  74. package/src/fonts/black-ops-one.ts +49 -0
  75. package/src/fonts/bree-serif.ts +34 -0
  76. package/src/fonts/bricolage-grotesque.ts +66 -0
  77. package/src/fonts/cabin.ts +114 -0
  78. package/src/fonts/cairo.ts +66 -0
  79. package/src/fonts/cardo.ts +193 -0
  80. package/src/fonts/catamaran.ts +41 -0
  81. package/src/fonts/caveat.ts +78 -0
  82. package/src/fonts/chakra-petch.ts +142 -0
  83. package/src/fonts/chivo.ts +66 -0
  84. package/src/fonts/cinzel.ts +50 -0
  85. package/src/fonts/comfortaa.ts +109 -0
  86. package/src/fonts/commissioner.ts +64 -0
  87. package/src/fonts/component.tsx +27 -0
  88. package/src/fonts/cormorant-garamond.ts +171 -0
  89. package/src/fonts/cormorant.ts +170 -0
  90. package/src/fonts/courgette.ts +34 -0
  91. package/src/fonts/courier-prime.ts +82 -0
  92. package/src/fonts/create-font.ts +47 -0
  93. package/src/fonts/create-registry-font.ts +40 -0
  94. package/src/fonts/crete-round.ts +50 -0
  95. package/src/fonts/crimson-text.ts +114 -0
  96. package/src/fonts/css.ts +86 -0
  97. package/src/fonts/dancing-script.ts +66 -0
  98. package/src/fonts/dm-mono.ts +50 -0
  99. package/src/fonts/dm-sans.ts +50 -0
  100. package/src/fonts/dm-serif-display.ts +50 -0
  101. package/src/fonts/domine.ts +50 -0
  102. package/src/fonts/dosis.ts +66 -0
  103. package/src/fonts/eb-garamond.ts +228 -0
  104. package/src/fonts/eczar.ts +94 -0
  105. package/src/fonts/encode-sans.ts +42 -0
  106. package/src/fonts/epilogue.ts +66 -0
  107. package/src/fonts/exo-2.ts +94 -0
  108. package/src/fonts/exo.ts +66 -0
  109. package/src/fonts/figtree.ts +82 -0
  110. package/src/fonts/fira-code.ts +121 -0
  111. package/src/fonts/fira-sans-condensed.ts +228 -0
  112. package/src/fonts/fira-sans.ts +228 -0
  113. package/src/fonts/fjalla-one.ts +49 -0
  114. package/src/fonts/font-urls.gen.json +1962 -0
  115. package/src/fonts/fraunces.ts +66 -0
  116. package/src/fonts/fredoka.ts +64 -0
  117. package/src/fonts/gelasio.ts +114 -0
  118. package/src/fonts/gloria-hallelujah.ts +34 -0
  119. package/src/fonts/gothic-a1.ts +124 -0
  120. package/src/fonts/great-vibes.ts +64 -0
  121. package/src/fonts/gudea.ts +66 -0
  122. package/src/fonts/heebo.ts +57 -0
  123. package/src/fonts/hind-siliguri.ts +66 -0
  124. package/src/fonts/hind.ts +66 -0
  125. package/src/fonts/ibm-plex-mono.ts +172 -0
  126. package/src/fonts/ibm-plex-sans-arabic.ts +80 -0
  127. package/src/fonts/ibm-plex-sans.ts +199 -0
  128. package/src/fonts/ibm-plex-serif.ts +171 -0
  129. package/src/fonts/inconsolata.ts +66 -0
  130. package/src/fonts/index.ts +4 -0
  131. package/src/fonts/indie-flower.ts +34 -0
  132. package/src/fonts/instrument-sans.ts +82 -0
  133. package/src/fonts/instrument-serif.ts +50 -0
  134. package/src/fonts/inter-display.ts +52 -0
  135. package/src/fonts/inter-tight.ts +124 -0
  136. package/src/fonts/inter.ts +123 -0
  137. package/src/fonts/jetbrains-mono.ts +199 -0
  138. package/src/fonts/josefin-sans.ts +114 -0
  139. package/src/fonts/josefin-slab.ts +50 -0
  140. package/src/fonts/jost.ts +64 -0
  141. package/src/fonts/kalam.ts +66 -0
  142. package/src/fonts/kanit.ts +142 -0
  143. package/src/fonts/karla.ts +82 -0
  144. package/src/fonts/khand.ts +66 -0
  145. package/src/fonts/lato.ts +82 -0
  146. package/src/fonts/league-spartan.ts +42 -0
  147. package/src/fonts/lexend-deca.ts +42 -0
  148. package/src/fonts/lexend.ts +42 -0
  149. package/src/fonts/libre-baskerville.ts +82 -0
  150. package/src/fonts/libre-franklin.ts +95 -0
  151. package/src/fonts/lilita-one.ts +34 -0
  152. package/src/fonts/lobster-two.ts +50 -0
  153. package/src/fonts/lobster.ts +56 -0
  154. package/src/fonts/lora.ts +235 -0
  155. package/src/fonts/m-plus-rounded-1c.ts +138 -0
  156. package/src/fonts/manrope.ts +109 -0
  157. package/src/fonts/maven-pro.ts +66 -0
  158. package/src/fonts/merriweather-sans.ts +142 -0
  159. package/src/fonts/merriweather.ts +171 -0
  160. package/src/fonts/montserrat-alternates.ts +171 -0
  161. package/src/fonts/montserrat.ts +94 -0
  162. package/src/fonts/mukta.ts +66 -0
  163. package/src/fonts/mulish.ts +170 -0
  164. package/src/fonts/nanum-gothic.ts +34 -0
  165. package/src/fonts/nanum-myeongjo.ts +34 -0
  166. package/src/fonts/neuton.ts +66 -0
  167. package/src/fonts/newsreader.ts +114 -0
  168. package/src/fonts/noticia-text.ts +114 -0
  169. package/src/fonts/noto-sans-arabic.ts +58 -0
  170. package/src/fonts/noto-sans-hk.ts +49 -0
  171. package/src/fonts/noto-sans-jp.ts +49 -0
  172. package/src/fonts/noto-sans-kr.ts +49 -0
  173. package/src/fonts/noto-sans-sc.ts +49 -0
  174. package/src/fonts/noto-sans-tc.ts +49 -0
  175. package/src/fonts/noto-sans.ts +140 -0
  176. package/src/fonts/noto-serif-jp.ts +80 -0
  177. package/src/fonts/noto-serif.ts +140 -0
  178. package/src/fonts/nunito-sans.ts +170 -0
  179. package/src/fonts/nunito.ts +170 -0
  180. package/src/fonts/old-standard-tt.ts +133 -0
  181. package/src/fonts/onest.ts +48 -0
  182. package/src/fonts/open-sans.ts +340 -0
  183. package/src/fonts/orbitron.ts +34 -0
  184. package/src/fonts/oswald.ts +94 -0
  185. package/src/fonts/outfit.ts +34 -0
  186. package/src/fonts/overpass.ts +94 -0
  187. package/src/fonts/oxygen.ts +50 -0
  188. package/src/fonts/pacifico.ts +56 -0
  189. package/src/fonts/passion-one.ts +50 -0
  190. package/src/fonts/pathway-gothic-one.ts +34 -0
  191. package/src/fonts/permanent-marker.ts +26 -0
  192. package/src/fonts/philosopher.ts +172 -0
  193. package/src/fonts/play.ts +109 -0
  194. package/src/fonts/playfair-display.ts +142 -0
  195. package/src/fonts/plus-jakarta-sans.ts +142 -0
  196. package/src/fonts/poppins.ts +114 -0
  197. package/src/fonts/prompt.ts +142 -0
  198. package/src/fonts/pt-sans-narrow.ts +78 -0
  199. package/src/fonts/pt-sans.ts +138 -0
  200. package/src/fonts/pt-serif.ts +138 -0
  201. package/src/fonts/public-sans.ts +66 -0
  202. package/src/fonts/questrial.ts +42 -0
  203. package/src/fonts/quicksand.ts +66 -0
  204. package/src/fonts/rajdhani.ts +66 -0
  205. package/src/fonts/raleway.ts +94 -0
  206. package/src/fonts/recursive.ts +80 -0
  207. package/src/fonts/red-hat-display.ts +82 -0
  208. package/src/fonts/red-rose.ts +66 -0
  209. package/src/fonts/registry.gen.ts +173 -0
  210. package/src/fonts/righteous.ts +34 -0
  211. package/src/fonts/roboto-condensed.ts +124 -0
  212. package/src/fonts/roboto-flex.ts +64 -0
  213. package/src/fonts/roboto-mono.ts +199 -0
  214. package/src/fonts/roboto-slab.ts +72 -0
  215. package/src/fonts/roboto.ts +156 -0
  216. package/src/fonts/rokkitt.ts +66 -0
  217. package/src/fonts/rubik.ts +199 -0
  218. package/src/fonts/sacramento.ts +34 -0
  219. package/src/fonts/saira.ts +66 -0
  220. package/src/fonts/sarabun.ts +142 -0
  221. package/src/fonts/satisfy.ts +26 -0
  222. package/src/fonts/sawarabi-mincho.ts +41 -0
  223. package/src/fonts/shadows-into-light.ts +34 -0
  224. package/src/fonts/signika-negative.ts +66 -0
  225. package/src/fonts/signika.ts +66 -0
  226. package/src/fonts/slabo-27px.ts +34 -0
  227. package/src/fonts/sora.ts +50 -0
  228. package/src/fonts/source-code-pro.ts +228 -0
  229. package/src/fonts/source-sans-3.ts +228 -0
  230. package/src/fonts/source-serif-4.ts +199 -0
  231. package/src/fonts/space-grotesk.ts +66 -0
  232. package/src/fonts/space-mono.ts +114 -0
  233. package/src/fonts/spectral.ts +170 -0
  234. package/src/fonts/tajawal.ts +50 -0
  235. package/src/fonts/teko.ts +66 -0
  236. package/src/fonts/tenor-sans.ts +41 -0
  237. package/src/fonts/tinos.ts +256 -0
  238. package/src/fonts/titillium-web.ts +82 -0
  239. package/src/fonts/types.ts +43 -0
  240. package/src/fonts/ubuntu-condensed.ts +63 -0
  241. package/src/fonts/ubuntu-mono.ts +195 -0
  242. package/src/fonts/ubuntu.ts +195 -0
  243. package/src/fonts/unbounded.ts +94 -0
  244. package/src/fonts/unicode-ranges.gen.ts +28 -0
  245. package/src/fonts/urbanist.ts +50 -0
  246. package/src/fonts/varela-round.ts +49 -0
  247. package/src/fonts/vollkorn.ts +199 -0
  248. package/src/fonts/work-sans.ts +66 -0
  249. package/src/fonts/yanone-kaffeesatz.ts +128 -0
  250. package/src/fonts/yantramanav.ts +66 -0
  251. package/src/fonts/yellowtail.ts +34 -0
  252. package/src/fonts/young-serif.ts +34 -0
  253. package/src/fonts/zen-kaku-gothic-new.ts +64 -0
  254. package/src/fonts/zilla-slab.ts +82 -0
  255. package/src/form/index.tsx +205 -0
  256. package/src/generators/index.ts +1589 -0
  257. package/src/head-client/index.ts +649 -0
  258. package/src/history/index.ts +210 -0
  259. package/src/hooks.ts +61 -0
  260. package/src/hydrate/index.tsx +381 -0
  261. package/src/hydration/index.ts +187 -0
  262. package/src/i18n/index.ts +250 -0
  263. package/src/i18n/jsx.tsx +42 -0
  264. package/src/i18n/message-format.ts +174 -0
  265. package/src/image/index.tsx +473 -0
  266. package/src/image.ts +12 -0
  267. package/src/intercept-outlet/index.tsx +18 -0
  268. package/src/intercept-outlet.ts +2 -0
  269. package/src/internal/index.ts +143 -0
  270. package/src/internal/once-idle.ts +24 -0
  271. package/src/internal.ts +12 -0
  272. package/src/lazy/index.tsx +205 -0
  273. package/src/link/index.tsx +367 -0
  274. package/src/loader-pipeline/index.ts +639 -0
  275. package/src/locale/index.tsx +137 -0
  276. package/src/locale.ts +3 -0
  277. package/src/logger.ts +22 -0
  278. package/src/middleware/builtins/api-proxy.ts +64 -0
  279. package/src/middleware/builtins/cdn-proxy.ts +58 -0
  280. package/src/middleware/builtins/i18n.ts +256 -0
  281. package/src/middleware/builtins/keepalive.ts +24 -0
  282. package/src/middleware/builtins/markdown-negotiation.ts +141 -0
  283. package/src/middleware/builtins/static-assets.ts +32 -0
  284. package/src/middleware/index.ts +223 -0
  285. package/src/module-graph/index.ts +181 -0
  286. package/src/mount/index.ts +146 -0
  287. package/src/navigate.ts +1 -0
  288. package/src/navigation/index.ts +1278 -0
  289. package/src/navigation/types.ts +59 -0
  290. package/src/navigation-progress/index.tsx +247 -0
  291. package/src/navigation-progress.ts +1 -0
  292. package/src/ndjson-client/index.ts +376 -0
  293. package/src/ndjson-server/index.ts +284 -0
  294. package/src/outlet/index.tsx +633 -0
  295. package/src/outlet/types.ts +243 -0
  296. package/src/plugins/css-transform.ts +70 -0
  297. package/src/plugins/dev-cdn-cache.ts +454 -0
  298. package/src/plugins/dev-dashboard/client.tsx +2603 -0
  299. package/src/plugins/dev-dashboard/match-route-tree.ts +159 -0
  300. package/src/plugins/dev-dashboard/plugin.ts +404 -0
  301. package/src/plugins/dev-prerender.ts +168 -0
  302. package/src/plugins/dev-server.ts +327 -0
  303. package/src/plugins/generate-watch.ts +32 -0
  304. package/src/plugins/image-plugin.ts +171 -0
  305. package/src/plugins/index.ts +445 -0
  306. package/src/plugins/options.ts +37 -0
  307. package/src/plugins/parse-utils.ts +59 -0
  308. package/src/plugins/prerender-plugin.ts +144 -0
  309. package/src/plugins/purge.ts +242 -0
  310. package/src/plugins/server-fn.ts +641 -0
  311. package/src/plugins/service-worker.ts +225 -0
  312. package/src/plugins/solid-jsx.d.ts +12 -0
  313. package/src/plugins/sx-ast/atomic.ts +166 -0
  314. package/src/plugins/sx-ast/evaluator.ts +235 -0
  315. package/src/plugins/sx-ast/index.ts +372 -0
  316. package/src/plugins/sx-ast/parser.ts +22 -0
  317. package/src/plugins/sx-ast/rewrite.ts +763 -0
  318. package/src/plugins/sx.d.ts +14 -0
  319. package/src/plugins/tw-compile.ts +240 -0
  320. package/src/plugins/types.ts +41 -0
  321. package/src/plugins/virtual.d.ts +110 -0
  322. package/src/plugins/virtual.ts +184 -0
  323. package/src/preload/index.ts +77 -0
  324. package/src/prerender/index.ts +431 -0
  325. package/src/query-client/index.tsx +168 -0
  326. package/src/registry/index.ts +88 -0
  327. package/src/reset-css.ts +1 -0
  328. package/src/revalidation/index.ts +84 -0
  329. package/src/rewrite/index.ts +83 -0
  330. package/src/route-builder/create-layout.ts +655 -0
  331. package/src/route-builder/create-page.ts +806 -0
  332. package/src/route-builder/create-path-segment.ts +34 -0
  333. package/src/route-builder/create-root-layout.ts +608 -0
  334. package/src/route-builder/index.ts +49 -0
  335. package/src/route-builder/register.ts +242 -0
  336. package/src/route-builder/types.ts +294 -0
  337. package/src/router-config/index.ts +91 -0
  338. package/src/router-primitives/index.ts +25 -0
  339. package/src/router-primitives/location.ts +29 -0
  340. package/src/router-primitives/match-id.ts +111 -0
  341. package/src/router-primitives/paths.ts +109 -0
  342. package/src/router-primitives/tree.ts +243 -0
  343. package/src/router-primitives/types.ts +74 -0
  344. package/src/router.ts +31 -0
  345. package/src/search-engine/bing.ts +32 -0
  346. package/src/search-engine/google-jwt.ts +95 -0
  347. package/src/search-engine/google.ts +139 -0
  348. package/src/search-engine/index-now.ts +64 -0
  349. package/src/search-engine/index.ts +12 -0
  350. package/src/security/index.ts +230 -0
  351. package/src/server/index.ts +286 -0
  352. package/src/server-context/browser.ts +113 -0
  353. package/src/server-context/index.ts +217 -0
  354. package/src/server-fn/index.ts +710 -0
  355. package/src/server-fn-query.ts +169 -0
  356. package/src/server-handler/etag.ts +51 -0
  357. package/src/server-handler/headers.ts +5 -0
  358. package/src/server-handler/index.ts +1664 -0
  359. package/src/server-handler/types.ts +24 -0
  360. package/src/server-handler/validate-static-params.ts +130 -0
  361. package/src/server.ts +2 -0
  362. package/src/service-worker/template.ts +153 -0
  363. package/src/service-worker-hook/index.ts +70 -0
  364. package/src/sitemap/index.ts +231 -0
  365. package/src/ssr/critical-css.ts +150 -0
  366. package/src/ssr/head-prefix.ts +65 -0
  367. package/src/ssr/head.ts +259 -0
  368. package/src/ssr/index.tsx +879 -0
  369. package/src/state-parser/index.ts +266 -0
  370. package/src/store/filesystem.ts +131 -0
  371. package/src/store/index.ts +29 -0
  372. package/src/styles/index.ts +1200 -0
  373. package/src/styles/sx-types.ts +11 -0
  374. package/src/testing/index.ts +251 -0
  375. package/src/testing.ts +2 -0
  376. package/src/theme/index.tsx +232 -0
  377. package/src/theme.ts +3 -0
  378. package/src/tracing/index.ts +4 -0
  379. package/src/tracing/noop.ts +13 -0
  380. package/src/tracing/otel.ts +37 -0
  381. package/src/tracing/timing.ts +34 -0
  382. package/src/tracing/types.ts +9 -0
  383. package/src/url/index.ts +140 -0
  384. package/src/use-router.ts +11 -0
  385. package/src/use-suspense-query.ts +2 -0
  386. package/src/validation/index.ts +57 -0
  387. package/src/view-transition-css.ts +1 -0
@@ -0,0 +1,649 @@
1
+ import type { HeadConfig, RobotsConfig } from "../route-builder/types.ts";
2
+
3
+ export interface PerRouteHead {
4
+ head: HeadConfig;
5
+ matchId: string;
6
+ }
7
+
8
+ const TITLE_MARKER = "__title__";
9
+ const CSS_MARKER_PREFIX = "__css__:";
10
+
11
+ let baseTitle = "";
12
+ let currentRouteHierarchy: string[] = [];
13
+ const headByRoute = new Map<string, Set<string>>();
14
+ const managedMetaTags = new Set<string>();
15
+ const managedHreflangTags = new Set<string>();
16
+
17
+ /* CSS lifecycle: per-route elements + refcounted dedup for shared stylesheets */
18
+ const cssElementsByRoute = new Map<string, HTMLElement[]>();
19
+ const cssRefCount = new Map<string, { element: HTMLElement; refCount: number }>();
20
+
21
+ export function clearRouteTracking(): void {
22
+ baseTitle = "";
23
+ currentRouteHierarchy = [];
24
+ headByRoute.clear();
25
+ managedMetaTags.clear();
26
+ managedHreflangTags.clear();
27
+ cssElementsByRoute.clear();
28
+ cssRefCount.clear();
29
+ }
30
+
31
+ export function initRouteHierarchy(matchIds: string[], rootTitle?: string): void {
32
+ currentRouteHierarchy = [...matchIds];
33
+
34
+ if (typeof document === "undefined") return;
35
+
36
+ /* Use root layout title as fallback, not document.title which is the merged SSR title */
37
+ baseTitle = rootTitle ?? document.title;
38
+
39
+ for (const meta of document.head.querySelectorAll("meta[name], meta[property]")) {
40
+ const sel = buildMetaSelector(meta as Element);
41
+ if (sel) managedMetaTags.add(sel);
42
+ }
43
+
44
+ for (const link of document.head.querySelectorAll('link[rel="alternate"][hreflang]')) {
45
+ const hreflang = (link as Element).getAttribute("hreflang");
46
+ if (hreflang) managedHreflangTags.add(hreflang);
47
+ }
48
+
49
+ /*
50
+ * Don't pre-populate headByRoute here. The subsequent applyPerRouteHeads()
51
+ * call during hydration will build headByRoute entries from actual head
52
+ * configs, avoiding capture of infrastructure tags (viewport, charset).
53
+ */
54
+ }
55
+
56
+ export function applyPerRouteHeads(heads: PerRouteHead[]): void {
57
+ const newHierarchy = heads.map((h) => h.matchId);
58
+ const newSet = new Set(newHierarchy);
59
+ const removed = currentRouteHierarchy.filter((r) => !newSet.has(r));
60
+
61
+ for (const routeId of removed) {
62
+ const selectors = headByRoute.get(routeId);
63
+ if (selectors) {
64
+ for (const sel of selectors) {
65
+ if (sel !== TITLE_MARKER && !sel.startsWith(CSS_MARKER_PREFIX)) {
66
+ removeBySelector(sel);
67
+ managedMetaTags.delete(sel);
68
+ }
69
+ }
70
+ headByRoute.delete(routeId);
71
+ }
72
+ removeCssForRoute(routeId);
73
+ }
74
+
75
+ for (const { head, matchId } of heads) {
76
+ applyHeadConfigForRoute(matchId, head);
77
+ }
78
+
79
+ /* If no route in the new hierarchy set a title, restore the base (root layout) title */
80
+ let titleSet = false;
81
+ for (const matchId of newHierarchy) {
82
+ if (headByRoute.get(matchId)?.has(TITLE_MARKER)) {
83
+ titleSet = true;
84
+ break;
85
+ }
86
+ }
87
+ if (!titleSet && typeof document !== "undefined") {
88
+ document.title = baseTitle;
89
+ }
90
+
91
+ currentRouteHierarchy = newHierarchy;
92
+ }
93
+
94
+ export function applyHeadConfig(config: HeadConfig): void {
95
+ const currentTags = new Set<string>();
96
+ const currentHreflangTags = new Set<string>();
97
+
98
+ applyHeadFields(config, currentTags, currentHreflangTags);
99
+
100
+ for (const sel of managedMetaTags) {
101
+ if (!currentTags.has(sel)) {
102
+ removeBySelector(sel);
103
+ }
104
+ }
105
+ managedMetaTags.clear();
106
+ for (const sel of currentTags) {
107
+ managedMetaTags.add(sel);
108
+ }
109
+
110
+ for (const lang of managedHreflangTags) {
111
+ if (!currentHreflangTags.has(lang)) {
112
+ removeBySelector(`link[hreflang="${escapeAttr(lang)}"]`);
113
+ }
114
+ }
115
+ managedHreflangTags.clear();
116
+ for (const lang of currentHreflangTags) {
117
+ managedHreflangTags.add(lang);
118
+ }
119
+ }
120
+
121
+ function applyHeadConfigForRoute(matchId: string, config: HeadConfig): void {
122
+ const oldSelectors = headByRoute.get(matchId) ?? new Set<string>();
123
+
124
+ const tracked = new Set<string>();
125
+ const hreflangTracked = new Set<string>();
126
+
127
+ applyHeadFields(config, tracked, hreflangTracked);
128
+
129
+ /* Remove selectors that the route no longer produces */
130
+ for (const sel of oldSelectors) {
131
+ if (!tracked.has(sel) && !sel.startsWith(CSS_MARKER_PREFIX)) {
132
+ removeBySelector(sel);
133
+ managedMetaTags.delete(sel);
134
+ }
135
+ }
136
+
137
+ /* CSS lifecycle: reconcile CSS elements for this route */
138
+ applyCssForRoute(matchId, config);
139
+
140
+ headByRoute.set(matchId, tracked);
141
+ for (const sel of tracked) {
142
+ managedMetaTags.add(sel);
143
+ }
144
+ }
145
+
146
+ function applyHeadFields(config: HeadConfig, tracked: Set<string>, hreflangTracked: Set<string>): void {
147
+ if (config.title !== undefined) {
148
+ document.title = config.title;
149
+ tracked.add(TITLE_MARKER);
150
+ }
151
+
152
+ if (config.description !== undefined) {
153
+ upsertMeta("name", "description", config.description, tracked);
154
+ }
155
+
156
+ if (config.keywords !== undefined) {
157
+ upsertMeta("name", "keywords", config.keywords, tracked);
158
+ }
159
+
160
+ if (config.robots) {
161
+ upsertMeta("name", "robots", buildRobotsContent(config.robots), tracked);
162
+ }
163
+
164
+ if (config.canonical !== undefined) {
165
+ upsertLink("canonical", config.canonical, tracked);
166
+ }
167
+
168
+ /* OG images — top-level head.images + openGraph.images both produce og:image (matching SSR) */
169
+ const ogImages = [...(config.images ?? []), ...(config.openGraph?.images ?? [])];
170
+ if (ogImages.length > 0) {
171
+ const ogImgSels = [
172
+ 'meta[property="og:image"]',
173
+ 'meta[property="og:image:width"]',
174
+ 'meta[property="og:image:height"]',
175
+ 'meta[property="og:image:alt"]',
176
+ 'meta[property="og:image:type"]',
177
+ ];
178
+ for (const sel of ogImgSels) removeBySelector(sel);
179
+ for (const img of ogImages) {
180
+ appendMeta("property", "og:image", img.url, tracked);
181
+ if (img.width) appendMeta("property", "og:image:width", String(img.width), tracked);
182
+ if (img.height) appendMeta("property", "og:image:height", String(img.height), tracked);
183
+ if (img.alt) appendMeta("property", "og:image:alt", img.alt, tracked);
184
+ if (img.type) appendMeta("property", "og:image:type", img.type, tracked);
185
+ }
186
+ }
187
+
188
+ if (config.openGraph) {
189
+ const og = config.openGraph;
190
+ if (og.title) upsertMeta("property", "og:title", og.title, tracked);
191
+ if (og.description) upsertMeta("property", "og:description", og.description, tracked);
192
+ if (og.url) upsertMeta("property", "og:url", og.url, tracked);
193
+ if (og.type) upsertMeta("property", "og:type", og.type, tracked);
194
+ if (og.siteName) upsertMeta("property", "og:site_name", og.siteName, tracked);
195
+ if (og.locale) upsertMeta("property", "og:locale", og.locale, tracked);
196
+ if (og.alternateLocale) {
197
+ const altSels = ['meta[property="og:locale:alternate"]'];
198
+ for (const sel of altSels) removeBySelector(sel);
199
+ for (const locale of og.alternateLocale) {
200
+ appendMeta("property", "og:locale:alternate", locale, tracked);
201
+ }
202
+ }
203
+ if (og.videos) {
204
+ const ogVidSels = [
205
+ 'meta[property="og:video"]',
206
+ 'meta[property="og:video:secure_url"]',
207
+ 'meta[property="og:video:type"]',
208
+ 'meta[property="og:video:width"]',
209
+ 'meta[property="og:video:height"]',
210
+ ];
211
+ for (const sel of ogVidSels) removeBySelector(sel);
212
+ for (const video of og.videos) {
213
+ appendMeta("property", "og:video", video.url, tracked);
214
+ if (video.secureUrl) appendMeta("property", "og:video:secure_url", video.secureUrl, tracked);
215
+ if (video.type) appendMeta("property", "og:video:type", video.type, tracked);
216
+ if (video.width) appendMeta("property", "og:video:width", String(video.width), tracked);
217
+ if (video.height) appendMeta("property", "og:video:height", String(video.height), tracked);
218
+ }
219
+ }
220
+ if (og.audio) {
221
+ const ogAudioSels = [
222
+ 'meta[property="og:audio"]',
223
+ 'meta[property="og:audio:secure_url"]',
224
+ 'meta[property="og:audio:type"]',
225
+ ];
226
+ for (const sel of ogAudioSels) removeBySelector(sel);
227
+ for (const audio of og.audio) {
228
+ appendMeta("property", "og:audio", audio.url, tracked);
229
+ if (audio.secureUrl) appendMeta("property", "og:audio:secure_url", audio.secureUrl, tracked);
230
+ if (audio.type) appendMeta("property", "og:audio:type", audio.type, tracked);
231
+ }
232
+ }
233
+ }
234
+
235
+ if (config.twitter) {
236
+ const tw = config.twitter;
237
+ if (tw.card) upsertMeta("name", "twitter:card", tw.card, tracked);
238
+ if (tw.title) upsertMeta("name", "twitter:title", tw.title, tracked);
239
+ if (tw.description) upsertMeta("name", "twitter:description", tw.description, tracked);
240
+ if (tw.site) upsertMeta("name", "twitter:site", tw.site, tracked);
241
+ if (tw.creator) upsertMeta("name", "twitter:creator", tw.creator, tracked);
242
+ if (tw.images) {
243
+ const twImgSels = ['meta[name="twitter:image"]', 'meta[name="twitter:image:alt"]'];
244
+ for (const sel of twImgSels) removeBySelector(sel);
245
+ for (const img of tw.images) {
246
+ appendMeta("name", "twitter:image", img.url, tracked);
247
+ if (img.alt) appendMeta("name", "twitter:image:alt", img.alt, tracked);
248
+ }
249
+ }
250
+ }
251
+
252
+ if (config.favicons) {
253
+ const fav = config.favicons;
254
+ if (fav.ico) {
255
+ upsertFavicon("icon", fav.ico, { sizes: "any" }, tracked);
256
+ }
257
+ if (fav.svg) {
258
+ upsertFavicon("icon", fav.svg, { type: "image/svg+xml" }, tracked);
259
+ }
260
+ if (fav.appleTouchIcon) {
261
+ upsertFavicon("apple-touch-icon", fav.appleTouchIcon, {}, tracked);
262
+ }
263
+ const sizedKeys = ["96x96", "192x192", "512x512"] as const;
264
+ for (const size of sizedKeys) {
265
+ const href = fav[size];
266
+ if (href !== undefined) {
267
+ upsertFavicon("icon", href, { sizes: size, type: "image/png" }, tracked);
268
+ }
269
+ }
270
+ }
271
+
272
+ if (config.languages) {
273
+ for (const [lang, href] of Object.entries(config.languages)) {
274
+ upsertHreflang(lang, href, tracked, hreflangTracked);
275
+ }
276
+ }
277
+
278
+ if (config.jsonLd) {
279
+ upsertJsonLd(config.jsonLd, tracked);
280
+ } else {
281
+ /* Clean up SSR-rendered JSON-LD scripts not tracked in managedMetaTags */
282
+ removeBySelector('script[type="application/ld+json"]');
283
+ }
284
+
285
+ /* head.meta fields (author, creator, publisher, viewport, apple*, etc.) */
286
+ if (config.meta) {
287
+ const m = config.meta;
288
+ if (m.viewport !== undefined && m.viewport !== false) {
289
+ upsertMeta("name", "viewport", m.viewport, tracked);
290
+ }
291
+ if (m.manifest !== undefined) {
292
+ upsertLink("manifest", m.manifest, tracked);
293
+ }
294
+ if (m.appleMobileWebAppCapable !== undefined) {
295
+ upsertMeta("name", "apple-mobile-web-app-capable", m.appleMobileWebAppCapable, tracked);
296
+ }
297
+ if (m.appleMobileWebAppStatusBarStyle !== undefined) {
298
+ upsertMeta("name", "apple-mobile-web-app-status-bar-style", m.appleMobileWebAppStatusBarStyle, tracked);
299
+ }
300
+ if (m.appleMobileWebAppTitle !== undefined) {
301
+ upsertMeta("name", "apple-mobile-web-app-title", m.appleMobileWebAppTitle, tracked);
302
+ }
303
+ const META_NAME_MAP: Record<string, string> = {
304
+ applicationName: "application-name",
305
+ mobileWebAppCapable: "mobile-web-app-capable",
306
+ };
307
+ const genericMetaKeys = [
308
+ "applicationName",
309
+ "author",
310
+ "creator",
311
+ "generator",
312
+ "mobileWebAppCapable",
313
+ "publisher",
314
+ ] as const;
315
+ for (const key of genericMetaKeys) {
316
+ const val = m[key];
317
+ if (val !== undefined) {
318
+ const name = META_NAME_MAP[key] ?? key;
319
+ upsertMeta("name", name, val, tracked);
320
+ }
321
+ }
322
+ }
323
+
324
+ /* custom.meta → <meta> elements */
325
+ if (config.custom?.meta) {
326
+ for (const meta of config.custom.meta) {
327
+ const name = meta["name"];
328
+ const property = meta["property"];
329
+ if (name) {
330
+ upsertMeta("name", name, meta["content"] ?? "", tracked);
331
+ } else if (property) {
332
+ upsertMeta("property", property, meta["content"] ?? "", tracked);
333
+ }
334
+ }
335
+ }
336
+
337
+ /*
338
+ * custom.scripts intentionally NOT handled during SPA nav.
339
+ * Scripts are side-effectful (execute on DOM append) and not idempotent.
340
+ * SSR-rendered scripts persist; new page scripts load on full page nav.
341
+ */
342
+
343
+ /* custom.links (non-stylesheet handled separately in CSS lifecycle) */
344
+ if (config.custom?.links) {
345
+ for (const link of config.custom.links) {
346
+ if (link["rel"] === "stylesheet") continue;
347
+ const rel = link["rel"] ?? "";
348
+ const href = link["href"] ?? "";
349
+ const sel = `link[rel="${escapeAttr(rel)}"][href="${escapeAttr(href)}"]`;
350
+ tracked.add(sel);
351
+ const existing = document.head.querySelector(sel);
352
+ if (existing) continue;
353
+ const el = document.createElement("link");
354
+ for (const [k, v] of Object.entries(link)) {
355
+ if (!isSafeAttrName(k)) continue;
356
+ el.setAttribute(k, v);
357
+ }
358
+ document.head.appendChild(el);
359
+ }
360
+ }
361
+
362
+ /* CSS lifecycle: head.css → <link rel="stylesheet"> */
363
+ if (config.css !== undefined) {
364
+ const cssItems = Array.isArray(config.css) ? config.css : [config.css];
365
+ for (const href of cssItems) {
366
+ tracked.add(`${CSS_MARKER_PREFIX}link:${href}`);
367
+ }
368
+ }
369
+
370
+ /* custom.styles → <style> elements */
371
+ if (config.custom?.styles) {
372
+ for (let i = 0; i < config.custom.styles.length; i++) {
373
+ tracked.add(`${CSS_MARKER_PREFIX}style:${i}`);
374
+ }
375
+ }
376
+
377
+ /* custom.links with rel="stylesheet" tracked as CSS */
378
+ if (config.custom?.links) {
379
+ for (const link of config.custom.links) {
380
+ if (link.rel === "stylesheet" && link.href) {
381
+ tracked.add(`${CSS_MARKER_PREFIX}link:${link.href}`);
382
+ }
383
+ }
384
+ }
385
+ }
386
+
387
+ function upsertMeta(attrName: "name" | "property", attrValue: string, content: string, tracked: Set<string>): void {
388
+ const escaped = escapeAttr(attrValue);
389
+ const selector = `meta[${attrName}="${escaped}"]`;
390
+ tracked.add(selector);
391
+
392
+ const existing = document.head.querySelector(selector);
393
+ if (existing) {
394
+ if (existing.getAttribute("content") !== content) {
395
+ existing.setAttribute("content", content);
396
+ }
397
+ return;
398
+ }
399
+
400
+ const el = document.createElement("meta");
401
+ el.setAttribute(attrName, attrValue);
402
+ el.setAttribute("content", content);
403
+ document.head.appendChild(el);
404
+ }
405
+
406
+ /* Always creates a new element — for multi-value OG properties (images, videos) */
407
+ function appendMeta(attrName: "name" | "property", attrValue: string, content: string, tracked: Set<string>): void {
408
+ const escaped = escapeAttr(attrValue);
409
+ const selector = `meta[${attrName}="${escaped}"]`;
410
+ tracked.add(selector);
411
+
412
+ const el = document.createElement("meta");
413
+ el.setAttribute(attrName, attrValue);
414
+ el.setAttribute("content", content);
415
+ document.head.appendChild(el);
416
+ }
417
+
418
+ function upsertLink(rel: string, href: string, tracked: Set<string>): void {
419
+ const selector = `link[rel="${escapeAttr(rel)}"]`;
420
+ tracked.add(selector);
421
+
422
+ const existing = document.head.querySelector(selector) as HTMLElement | null;
423
+ if (existing) {
424
+ if (existing.getAttribute("href") !== href) {
425
+ existing.setAttribute("href", href);
426
+ }
427
+ return;
428
+ }
429
+
430
+ const el = document.createElement("link");
431
+ el.setAttribute("rel", rel);
432
+ el.setAttribute("href", href);
433
+ document.head.appendChild(el);
434
+ }
435
+
436
+ function upsertFavicon(rel: string, href: string, attrs: Record<string, string>, tracked: Set<string>): void {
437
+ const extraParts = Object.entries(attrs)
438
+ .map(([k, v]) => `[${k}="${escapeAttr(v)}"]`)
439
+ .join("");
440
+ const selector = `link[rel="${escapeAttr(rel)}"]${extraParts}`;
441
+ tracked.add(selector);
442
+
443
+ const existing = document.head.querySelector(selector) as HTMLElement | null;
444
+ if (existing) {
445
+ if (existing.getAttribute("href") !== href) {
446
+ existing.setAttribute("href", href);
447
+ }
448
+ return;
449
+ }
450
+
451
+ const el = document.createElement("link");
452
+ el.setAttribute("rel", rel);
453
+ el.setAttribute("href", href);
454
+ for (const [k, v] of Object.entries(attrs)) {
455
+ el.setAttribute(k, v);
456
+ }
457
+ document.head.appendChild(el);
458
+ }
459
+
460
+ function upsertHreflang(lang: string, href: string, selectorTracked: Set<string>, langTracked: Set<string>): void {
461
+ langTracked.add(lang);
462
+
463
+ const selector = `link[hreflang="${escapeAttr(lang)}"]`;
464
+ selectorTracked.add(selector);
465
+ const existing = document.head.querySelector(selector) as HTMLElement | null;
466
+ if (existing) {
467
+ if (existing.getAttribute("href") !== href) {
468
+ existing.setAttribute("href", href);
469
+ }
470
+ return;
471
+ }
472
+
473
+ const el = document.createElement("link");
474
+ el.setAttribute("rel", "alternate");
475
+ el.setAttribute("hreflang", lang);
476
+ el.setAttribute("href", href);
477
+ document.head.appendChild(el);
478
+ }
479
+
480
+ function upsertJsonLd(items: Array<Record<string, unknown>>, tracked: Set<string>): void {
481
+ const selector = 'script[type="application/ld+json"]';
482
+ tracked.add(selector);
483
+
484
+ /* Remove ALL existing JSON-LD scripts — SSR may render multiple */
485
+ removeBySelector(selector);
486
+
487
+ const content = items.length === 1 ? JSON.stringify(items[0]) : JSON.stringify(items);
488
+ const el = document.createElement("script");
489
+ el.setAttribute("type", "application/ld+json");
490
+ el.textContent = content;
491
+ document.head.appendChild(el);
492
+ }
493
+
494
+ function buildRobotsContent(robots: RobotsConfig): string {
495
+ const directives: string[] = [];
496
+ if (robots.index === false) directives.push("noindex");
497
+ else if (robots.index === true) directives.push("index");
498
+ if (robots.follow === false) directives.push("nofollow");
499
+ else if (robots.follow === true) directives.push("follow");
500
+ if (robots.noarchive) directives.push("noarchive");
501
+ if (robots.noimageindex) directives.push("noimageindex");
502
+ if (robots["max-snippet"] !== undefined) directives.push(`max-snippet:${robots["max-snippet"]}`);
503
+ if (robots["max-image-preview"] !== undefined) directives.push(`max-image-preview:${robots["max-image-preview"]}`);
504
+ if (robots["max-video-preview"] !== undefined) directives.push(`max-video-preview:${robots["max-video-preview"]}`);
505
+ return directives.join(",");
506
+ }
507
+
508
+ const VALID_ATTR_RE = /^[a-zA-Z][a-zA-Z0-9-]*$/;
509
+
510
+ function isSafeAttrName(name: string): boolean {
511
+ if (!VALID_ATTR_RE.test(name)) return false;
512
+ return !name.toLowerCase().startsWith("on");
513
+ }
514
+
515
+ function escapeAttr(value: string): string {
516
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\]/g, "\\]").replace(/\0/g, "\\0");
517
+ }
518
+
519
+ function buildMetaSelector(el: Element): string | null {
520
+ const name = el.getAttribute("name");
521
+ if (name) return `meta[name="${escapeAttr(name)}"]`;
522
+ const prop = el.getAttribute("property");
523
+ if (prop) return `meta[property="${escapeAttr(prop)}"]`;
524
+ return null;
525
+ }
526
+
527
+ /* ── CSS lifecycle helpers ────────────────────────────────────────────── */
528
+
529
+ function addCssLinkForRoute(matchId: string, href: string): void {
530
+ if (typeof document === "undefined") return;
531
+ const ref = cssRefCount.get(href);
532
+ if (ref) {
533
+ ref.refCount++;
534
+ const list = cssElementsByRoute.get(matchId) ?? [];
535
+ list.push(ref.element);
536
+ cssElementsByRoute.set(matchId, list);
537
+ return;
538
+ }
539
+
540
+ /* Adopt SSR-rendered or untracked element if already in DOM */
541
+ const existing = document.head.querySelector(
542
+ `link[rel="stylesheet"][href="${escapeAttr(href)}"]`,
543
+ ) as HTMLElement | null;
544
+ if (existing) {
545
+ existing.setAttribute("data-flare-route", matchId);
546
+ cssRefCount.set(href, { element: existing, refCount: 1 });
547
+ const list = cssElementsByRoute.get(matchId) ?? [];
548
+ list.push(existing);
549
+ cssElementsByRoute.set(matchId, list);
550
+ return;
551
+ }
552
+
553
+ const el = document.createElement("link");
554
+ el.setAttribute("rel", "stylesheet");
555
+ el.setAttribute("href", href);
556
+ el.setAttribute("data-flare-route", matchId);
557
+ document.head.appendChild(el);
558
+ cssRefCount.set(href, { element: el, refCount: 1 });
559
+ const list = cssElementsByRoute.get(matchId) ?? [];
560
+ list.push(el);
561
+ cssElementsByRoute.set(matchId, list);
562
+ }
563
+
564
+ function addCssStyleForRoute(matchId: string, children: string): void {
565
+ if (typeof document === "undefined") return;
566
+ const el = document.createElement("style");
567
+ el.setAttribute("data-flare-route", matchId);
568
+ el.textContent = children;
569
+ document.head.appendChild(el);
570
+ const list = cssElementsByRoute.get(matchId) ?? [];
571
+ list.push(el);
572
+ cssElementsByRoute.set(matchId, list);
573
+ }
574
+
575
+ function applyCssForRoute(matchId: string, config: HeadConfig): void {
576
+ /* Snapshot old elements before adding new ones */
577
+ const oldElements = cssElementsByRoute.get(matchId) ?? [];
578
+ cssElementsByRoute.delete(matchId);
579
+
580
+ /* ADD new CSS elements first — increments refcounts so shared stylesheets survive */
581
+ if (config.css !== undefined) {
582
+ const cssItems = Array.isArray(config.css) ? config.css : [config.css];
583
+ for (const href of cssItems) {
584
+ addCssLinkForRoute(matchId, href);
585
+ }
586
+ }
587
+
588
+ if (config.custom?.styles) {
589
+ for (const style of config.custom.styles) {
590
+ addCssStyleForRoute(matchId, style.children);
591
+ }
592
+ }
593
+
594
+ if (config.custom?.links) {
595
+ for (const link of config.custom.links) {
596
+ if (link.rel === "stylesheet" && link.href) {
597
+ addCssLinkForRoute(matchId, link.href);
598
+ }
599
+ }
600
+ }
601
+
602
+ /* THEN decrement old refcounts — shared elements stay because new refs were added above */
603
+ for (const el of oldElements) {
604
+ const href = el.getAttribute("href");
605
+ if (href && el.getAttribute("rel") === "stylesheet") {
606
+ const ref = cssRefCount.get(href);
607
+ if (ref) {
608
+ ref.refCount--;
609
+ if (ref.refCount <= 0) {
610
+ ref.element.remove();
611
+ cssRefCount.delete(href);
612
+ }
613
+ continue;
614
+ }
615
+ }
616
+ el.remove();
617
+ }
618
+ }
619
+
620
+ function removeCssForRoute(routeId: string): void {
621
+ const elements = cssElementsByRoute.get(routeId);
622
+ if (!elements) return;
623
+
624
+ for (const el of elements) {
625
+ const href = el.getAttribute("href");
626
+ /* Refcounted stylesheet links */
627
+ if (href && el.getAttribute("rel") === "stylesheet") {
628
+ const ref = cssRefCount.get(href);
629
+ if (ref) {
630
+ ref.refCount--;
631
+ if (ref.refCount <= 0) {
632
+ ref.element.remove();
633
+ cssRefCount.delete(href);
634
+ }
635
+ continue;
636
+ }
637
+ }
638
+ /* <style> elements or untracked links — just remove */
639
+ el.remove();
640
+ }
641
+ cssElementsByRoute.delete(routeId);
642
+ }
643
+
644
+ function removeBySelector(selector: string): void {
645
+ if (typeof document === "undefined") return;
646
+ document.head.querySelectorAll(selector).forEach((el) => {
647
+ el.remove();
648
+ });
649
+ }