@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,205 @@
1
+ import type { JSX } from "solid-js";
2
+ import { type Component, createSignal, onMount, Show, sharedConfig } from "solid-js";
3
+ import { retryImport } from "../internal.ts";
4
+ import { warn } from "../logger.ts";
5
+
6
+ export interface LazyOptions<P extends Record<string, unknown>> {
7
+ loader: () => Promise<{ default: Component<P> }>;
8
+ pending?: Component<P>;
9
+ }
10
+
11
+ export interface ClientLazyOptions<P extends Record<string, unknown>> {
12
+ eager?: boolean;
13
+ loader: () => Promise<{ default: Component<P> }>;
14
+ pending?: Component<P>;
15
+ }
16
+
17
+ const PENDING_KEY = "__FLARE_LAZY_PENDING__";
18
+ const LOADED_KEY = "__FLARE_LAZY_LOADED__";
19
+
20
+ function getGlobalPending(): Set<Promise<void>> {
21
+ const g = globalThis as Record<string, unknown>;
22
+ if (!g[PENDING_KEY]) g[PENDING_KEY] = new Set<Promise<void>>();
23
+ return g[PENDING_KEY] as Set<Promise<void>>;
24
+ }
25
+
26
+ function getGlobalLoaded(): Set<Promise<void>> {
27
+ const g = globalThis as Record<string, unknown>;
28
+ if (!g[LOADED_KEY]) g[LOADED_KEY] = new Set<Promise<void>>();
29
+ return g[LOADED_KEY] as Set<Promise<void>>;
30
+ }
31
+
32
+ /**
33
+ * Throws during component init — ErrorBoundary catches it.
34
+ * Must be rendered via Show/conditional so the throw happens during
35
+ * the component's initial render phase (not in an effect).
36
+ */
37
+ function ThrowError(props: { error: Error }): JSX.Element {
38
+ throw props.error;
39
+ }
40
+
41
+ export function lazy<P extends Record<string, unknown>>(options: LazyOptions<P>): Component<P> {
42
+ const { loader, pending } = options;
43
+ let loaded: Component<P> | undefined;
44
+ let loadError: Error | undefined;
45
+ let loadPromise: Promise<void> | undefined;
46
+
47
+ /* Start loading immediately at factory call */
48
+ loadPromise = retryImport(loader)
49
+ .then((mod) => {
50
+ loaded = mod.default;
51
+ getGlobalLoaded().add(loadPromise as Promise<void>);
52
+ getGlobalPending().delete(loadPromise as Promise<void>);
53
+ })
54
+ .catch((e: unknown) => {
55
+ loadError = e instanceof Error ? e : new Error(String(e));
56
+ getGlobalPending().delete(loadPromise as Promise<void>);
57
+ });
58
+ getGlobalPending().add(loadPromise);
59
+
60
+ return ((props: P) => {
61
+ const isSSR = !!sharedConfig.context;
62
+
63
+ /* Pre-known error at render time → throw from component body */
64
+ if (loadError) throw loadError;
65
+
66
+ const [component, setComponent] = createSignal<Component<P> | undefined>(isSSR ? undefined : loaded);
67
+ const [error, setError] = createSignal<Error | undefined>();
68
+
69
+ if (isSSR || !loaded) {
70
+ onMount(() => {
71
+ if (loadError) {
72
+ setError(loadError);
73
+ } else if (loaded) {
74
+ setComponent(() => loaded);
75
+ } else {
76
+ loadPromise?.then(() => {
77
+ if (loadError) setError(loadError);
78
+ else setComponent(() => loaded);
79
+ });
80
+ }
81
+ });
82
+ }
83
+
84
+ const PendingFallback = pending
85
+ ? (() => {
86
+ const P = pending;
87
+ return (<P {...props} />) as JSX.Element;
88
+ })()
89
+ : null;
90
+
91
+ return (
92
+ <Show fallback={<ThrowError error={error() as Error} />} when={!error()}>
93
+ <Show fallback={PendingFallback} when={component()}>
94
+ {(Comp) => {
95
+ const C = Comp();
96
+ return (<C {...props} />) as JSX.Element;
97
+ }}
98
+ </Show>
99
+ </Show>
100
+ ) as JSX.Element;
101
+ }) as Component<P>;
102
+ }
103
+
104
+ export function clientLazy<P extends Record<string, unknown>>(
105
+ options: ClientLazyOptions<P>,
106
+ ): Component<P & { pending?: Component<P> }> {
107
+ const { eager, loader, pending: factoryPending } = options;
108
+ let loaded: Component<P> | undefined;
109
+ let loadError: Error | undefined;
110
+ let loadPromise: Promise<void> | undefined;
111
+
112
+ if (eager) {
113
+ loadPromise = retryImport(loader)
114
+ .then((mod) => {
115
+ loaded = mod.default;
116
+ })
117
+ .catch((e: unknown) => {
118
+ loadError = e instanceof Error ? e : new Error(String(e));
119
+ });
120
+ }
121
+
122
+ return ((props: P & { pending?: Component<P> }) => {
123
+ const isSSR = !!sharedConfig.context;
124
+
125
+ /* Pre-known error at render time → throw from component body */
126
+ if (loadError) throw loadError;
127
+
128
+ const [component, setComponent] = createSignal<Component<P> | undefined>(isSSR ? undefined : loaded);
129
+ const [error, setError] = createSignal<Error | undefined>();
130
+
131
+ const startLoading = () => {
132
+ if (!loadPromise) {
133
+ loadPromise = retryImport(loader)
134
+ .then((mod) => {
135
+ loaded = mod.default;
136
+ })
137
+ .catch((e: unknown) => {
138
+ loadError = e instanceof Error ? e : new Error(String(e));
139
+ });
140
+ }
141
+ loadPromise
142
+ .then(() => {
143
+ if (loadError) setError(loadError);
144
+ else setComponent(() => loaded);
145
+ })
146
+ .catch((e: unknown) => {
147
+ warn("lazy", "chunk retry failed", e);
148
+ });
149
+ };
150
+
151
+ if (!isSSR && !loaded) {
152
+ startLoading();
153
+ }
154
+
155
+ if (!isSSR && loaded) {
156
+ /* Already loaded — render immediately */
157
+ } else {
158
+ /* SSR hydration or not-yet-loaded: kick off after mount */
159
+ onMount(() => {
160
+ if (loaded) {
161
+ setComponent(() => loaded);
162
+ } else if (loadError) {
163
+ setError(loadError);
164
+ } else {
165
+ startLoading();
166
+ }
167
+ });
168
+ }
169
+
170
+ const PendingComp = props.pending ?? factoryPending;
171
+ const PendingFallback = PendingComp
172
+ ? (() => {
173
+ const P = PendingComp;
174
+ return (<P {...(props as P)} />) as JSX.Element;
175
+ })()
176
+ : null;
177
+
178
+ return (
179
+ <Show fallback={<ThrowError error={error() as Error} />} when={!error()}>
180
+ <Show fallback={PendingFallback} when={component()}>
181
+ {(Comp) => {
182
+ const C = Comp();
183
+ return (<C {...(props as P)} />) as JSX.Element;
184
+ }}
185
+ </Show>
186
+ </Show>
187
+ ) as JSX.Element;
188
+ }) as Component<P & { pending?: Component<P> }>;
189
+ }
190
+
191
+ export async function waitForLazyPreloads(): Promise<void> {
192
+ const pending = getGlobalPending();
193
+ if (pending.size === 0) return;
194
+ await Promise.all(pending);
195
+ }
196
+
197
+ /**
198
+ * Reset global tracking state. No-ops outside test environment.
199
+ */
200
+ export function resetLazyState(): void {
201
+ if (process.env.NODE_ENV !== "test") return;
202
+ const g = globalThis as Record<string, unknown>;
203
+ g[PENDING_KEY] = new Set<Promise<void>>();
204
+ g[LOADED_KEY] = new Set<Promise<void>>();
205
+ }
@@ -0,0 +1,367 @@
1
+ import { createMemo, type JSX, onCleanup, Show, splitProps } from "solid-js";
2
+ import { applyRewriteOutput, isExternal, navigate, prefetch } from "../navigation/index.ts";
3
+ import { useRouterContext } from "../outlet/index.tsx";
4
+ import type { RouteParamsProps, RoutePaths, RouteSearchProps } from "../route-builder/register.ts";
5
+ import { matchRoute, toLocaleMatch } from "../router-primitives/index.ts";
6
+ import { buildUrl } from "../url/index.ts";
7
+
8
+ export type PrefetchStrategy = false | "intent" | "render" | "viewport";
9
+
10
+ export type FlareAnchorProps = Omit<JSX.AnchorHTMLAttributes<HTMLAnchorElement>, "children" | "href">;
11
+
12
+ type InternalLinkProps<TPath extends RoutePaths = RoutePaths> = FlareAnchorProps & {
13
+ activeClass?: string;
14
+ activeProps?: FlareAnchorProps;
15
+ children: JSX.Element;
16
+ disabled?: boolean;
17
+ force?: boolean;
18
+ hash?: string;
19
+ href?: never;
20
+ inactiveClass?: string;
21
+ inactiveProps?: FlareAnchorProps;
22
+ isActive?: (location: { pathname: string }) => boolean;
23
+ prefetch?: PrefetchStrategy;
24
+ replace?: boolean;
25
+ revalidate?: boolean;
26
+ scroll?: boolean;
27
+ shallow?: boolean;
28
+ to: TPath;
29
+ viewTransition?: boolean | { types: string[] };
30
+ } & RouteParamsProps<TPath> &
31
+ RouteSearchProps<TPath>;
32
+
33
+ export type ExternalLinkProps = FlareAnchorProps & {
34
+ children: JSX.Element;
35
+ disabled?: boolean;
36
+ href: string;
37
+ to?: never;
38
+ };
39
+
40
+ export type LinkProps<TPath extends RoutePaths = RoutePaths> = InternalLinkProps<TPath> | ExternalLinkProps;
41
+
42
+ /* Flattened internal type for splitProps — avoids TS intersection issues with Omit */
43
+ interface LinkPropsInternal {
44
+ activeClass?: string;
45
+ activeProps?: FlareAnchorProps;
46
+ children: JSX.Element;
47
+ class?: string;
48
+ disabled?: boolean;
49
+ force?: boolean;
50
+ hash?: string;
51
+ href?: string;
52
+ inactiveClass?: string;
53
+ inactiveProps?: FlareAnchorProps;
54
+ isActive?: (location: { pathname: string }) => boolean;
55
+ params?: Record<string, unknown>;
56
+ prefetch?: PrefetchStrategy;
57
+ rel?: string;
58
+ replace?: boolean;
59
+ revalidate?: boolean;
60
+ scroll?: boolean;
61
+ search?: Record<string, unknown>;
62
+ shallow?: boolean;
63
+ style?: JSX.CSSProperties | string;
64
+ target?: string;
65
+ to?: string;
66
+ viewTransition?: boolean | { types: string[] };
67
+ }
68
+
69
+ const DANGEROUS_PROTOCOLS = ["javascript:", "data:", "blob:", "vbscript:"];
70
+ const LEADING_WHITESPACE_RE = /^[\s\u00A0\u200B\uFEFF\u2028\u2029]+/;
71
+
72
+ function isDangerousHref(href: string): boolean {
73
+ const stripped = href.replace(LEADING_WHITESPACE_RE, "").toLowerCase();
74
+ let decoded = stripped;
75
+ try {
76
+ decoded = decodeURIComponent(stripped);
77
+ } catch {
78
+ /* malformed percent-encoding — check raw only */
79
+ }
80
+ return DANGEROUS_PROTOCOLS.some((p) => stripped.startsWith(p) || decoded.startsWith(p));
81
+ }
82
+
83
+ export function Link<TPath extends RoutePaths>(props: LinkProps<TPath>): JSX.Element {
84
+ const ctx = useRouterContext();
85
+
86
+ const [local, rest] = splitProps(props as unknown as LinkPropsInternal, [
87
+ "activeClass",
88
+ "activeProps",
89
+ "children",
90
+ "class",
91
+ "disabled",
92
+ "force",
93
+ "hash",
94
+ "href",
95
+ "inactiveClass",
96
+ "inactiveProps",
97
+ "isActive",
98
+ "params",
99
+ "prefetch",
100
+ "rel",
101
+ "replace",
102
+ "revalidate",
103
+ "scroll",
104
+ "search",
105
+ "shallow",
106
+ "style",
107
+ "target",
108
+ "to",
109
+ "viewTransition",
110
+ ]);
111
+
112
+ const resolvedHref = createMemo(() => {
113
+ if (local.href !== undefined) {
114
+ if (isDangerousHref(local.href)) return "#";
115
+ return local.href;
116
+ }
117
+ const raw = buildUrl({
118
+ hash: local.hash,
119
+ params: local.params,
120
+ search: local.search,
121
+ to: local.to ?? "",
122
+ });
123
+ if (isDangerousHref(raw)) return "#";
124
+ return applyRewriteOutput(raw);
125
+ });
126
+
127
+ const effectiveRel = createMemo(() => {
128
+ if (local.rel) return local.rel;
129
+ if (local.target === "_blank") return "noopener noreferrer";
130
+ return undefined;
131
+ });
132
+
133
+ const routePrefetch = createMemo(() => {
134
+ if (local.href !== undefined) return undefined;
135
+ const h = resolvedHref();
136
+ if (isExternal(h)) return undefined;
137
+ try {
138
+ const url = new URL(h, typeof window !== "undefined" ? window.location.href : "http://localhost/");
139
+ const match = matchRoute(ctx.routeTree, url.pathname, ctx.caseSensitive, toLocaleMatch(ctx.localeConfig));
140
+ return match?.route.o.client?.prefetch;
141
+ } catch {
142
+ return undefined;
143
+ }
144
+ });
145
+
146
+ const effectivePrefetch = createMemo(() => {
147
+ if (local.href !== undefined) return false;
148
+ return local.prefetch ?? routePrefetch() ?? ctx.routerCacheDefaults?.prefetch ?? false;
149
+ });
150
+
151
+ const active = createMemo(() => {
152
+ if (local.href !== undefined) return false;
153
+ if (local.isActive) {
154
+ return local.isActive(ctx.location());
155
+ }
156
+ try {
157
+ const url = new URL(resolvedHref(), typeof window !== "undefined" ? window.location.href : "http://localhost/");
158
+ return url.pathname === ctx.location().pathname;
159
+ } catch {
160
+ return false;
161
+ }
162
+ });
163
+
164
+ const stateProps = createMemo(() => (active() ? local.activeProps : local.inactiveProps));
165
+
166
+ const classes = createMemo(() => {
167
+ const result: string[] = [];
168
+ if (typeof local.class === "string") result.push(local.class);
169
+ const sp = stateProps();
170
+ if (typeof sp?.class === "string") result.push(sp.class);
171
+ if (active()) {
172
+ if (local.activeClass) result.push(local.activeClass);
173
+ } else {
174
+ if (local.inactiveClass) result.push(local.inactiveClass);
175
+ }
176
+ return result.length > 0 ? result.join(" ") : undefined;
177
+ });
178
+
179
+ const mergedStyle = createMemo((): JSX.CSSProperties | string | undefined => {
180
+ const spStyle = stateProps()?.style;
181
+ if (!spStyle) return local.style || undefined;
182
+ if (!local.style) return spStyle || undefined;
183
+ if (typeof local.style === "object" && typeof spStyle === "object") {
184
+ return { ...local.style, ...spStyle };
185
+ }
186
+ if (typeof local.style === "string" && typeof spStyle === "string") {
187
+ return `${local.style};${spStyle}`;
188
+ }
189
+ return spStyle;
190
+ });
191
+
192
+ const stateAttrs = createMemo(() => {
193
+ const sp = stateProps();
194
+ if (!sp) return undefined;
195
+ const { class: _cls, style: _sty, ...attrs } = sp;
196
+ return Object.keys(attrs).length > 0 ? attrs : undefined;
197
+ });
198
+
199
+ function handleClick(event: MouseEvent): void {
200
+ if (local.disabled) {
201
+ event.preventDefault();
202
+ return;
203
+ }
204
+
205
+ if (event.defaultPrevented) return;
206
+
207
+ const h = resolvedHref();
208
+ if (isExternal(h)) return;
209
+ if (event.button !== 0) return;
210
+ if (event.metaKey || event.ctrlKey) return;
211
+ if (event.shiftKey) return;
212
+ if (event.altKey) return;
213
+ if (local.target && local.target !== "_self") return;
214
+
215
+ /* Allow native download behavior */
216
+ const el = event.currentTarget as HTMLAnchorElement;
217
+ if (el.hasAttribute("download")) return;
218
+
219
+ event.preventDefault();
220
+
221
+ if (
222
+ !local.force &&
223
+ typeof window !== "undefined" &&
224
+ h === window.location.pathname + window.location.search + window.location.hash
225
+ ) {
226
+ return;
227
+ }
228
+
229
+ navigate({
230
+ hash: local.hash,
231
+ params: local.params,
232
+ replace: local.replace,
233
+ revalidate: local.revalidate,
234
+ scroll: local.scroll,
235
+ search: local.search,
236
+ shallow: local.shallow,
237
+ to: local.to ?? "",
238
+ viewTransition: local.viewTransition,
239
+ });
240
+ }
241
+
242
+ function triggerPrefetch(): void {
243
+ if (local.disabled) return;
244
+ if (local.href !== undefined) return;
245
+ const h = resolvedHref();
246
+ if (isExternal(h)) return;
247
+ /* params + search already resolved into h by resolvedHref() — passing them again would double-apply */
248
+ prefetch({ to: h });
249
+ }
250
+
251
+ function handleIntent(): void {
252
+ if (effectivePrefetch() === "intent") triggerPrefetch();
253
+ }
254
+
255
+ function scheduleAfterLoad(fn: () => void): () => void {
256
+ let cancelled = false;
257
+ let idleId: number | undefined;
258
+ let timeoutId: ReturnType<typeof setTimeout> | undefined;
259
+
260
+ const run = () => {
261
+ if (!cancelled) fn();
262
+ };
263
+
264
+ const armIdle = () => {
265
+ if (cancelled) return;
266
+ if (typeof requestIdleCallback === "function") {
267
+ idleId = requestIdleCallback(run);
268
+ } else {
269
+ timeoutId = setTimeout(run, 0);
270
+ }
271
+ };
272
+
273
+ if (typeof document !== "undefined" && document.readyState === "complete") {
274
+ armIdle();
275
+ } else if (typeof window !== "undefined") {
276
+ addEventListener("load", armIdle, { once: true });
277
+ } else {
278
+ armIdle();
279
+ }
280
+
281
+ return () => {
282
+ cancelled = true;
283
+ if (typeof window !== "undefined") removeEventListener("load", armIdle);
284
+ if (idleId !== undefined && typeof cancelIdleCallback === "function") {
285
+ cancelIdleCallback(idleId);
286
+ }
287
+ if (timeoutId !== undefined) clearTimeout(timeoutId);
288
+ };
289
+ }
290
+
291
+ function setupPrefetchBehavior(el: HTMLAnchorElement): void {
292
+ if (local.href !== undefined) return;
293
+ if (isExternal(resolvedHref())) return;
294
+
295
+ const strategy = effectivePrefetch();
296
+
297
+ if (strategy === "intent") {
298
+ el.addEventListener("focus", handleIntent);
299
+ el.addEventListener("touchstart", handleIntent, { passive: true });
300
+ onCleanup(() => {
301
+ el.removeEventListener("focus", handleIntent);
302
+ el.removeEventListener("touchstart", handleIntent);
303
+ });
304
+ }
305
+
306
+ if (strategy === "viewport") {
307
+ let observer: IntersectionObserver | undefined;
308
+ const cancel = scheduleAfterLoad(() => {
309
+ if (typeof IntersectionObserver === "undefined") return;
310
+ observer = new IntersectionObserver(
311
+ (entries) => {
312
+ for (const entry of entries) {
313
+ if (entry.isIntersecting) {
314
+ triggerPrefetch();
315
+ observer?.unobserve(entry.target);
316
+ }
317
+ }
318
+ },
319
+ { threshold: 0 },
320
+ );
321
+ observer.observe(el);
322
+ });
323
+ onCleanup(() => {
324
+ cancel();
325
+ observer?.disconnect();
326
+ });
327
+ }
328
+
329
+ if (strategy === "render") {
330
+ const cancel = scheduleAfterLoad(() => triggerPrefetch());
331
+ onCleanup(cancel);
332
+ }
333
+ }
334
+
335
+ function disabledStyle(): JSX.CSSProperties | string {
336
+ if (typeof local.style === "string") return `cursor:not-allowed;${local.style}`;
337
+ if (local.style) return { cursor: "not-allowed", ...local.style };
338
+ return "cursor:not-allowed";
339
+ }
340
+
341
+ return (
342
+ <Show
343
+ fallback={
344
+ <span {...rest} aria-disabled="true" class={classes()} style={disabledStyle()} tabIndex={-1}>
345
+ {local.children}
346
+ </span>
347
+ }
348
+ when={!local.disabled}
349
+ >
350
+ <a
351
+ {...rest}
352
+ {...(stateAttrs() ?? {})}
353
+ {...(classes() ? { class: classes() } : {})}
354
+ {...(mergedStyle() ? { style: mergedStyle() } : {})}
355
+ aria-current={active() ? "page" : undefined}
356
+ href={resolvedHref()}
357
+ onClick={handleClick}
358
+ onMouseEnter={handleIntent}
359
+ ref={setupPrefetchBehavior}
360
+ rel={effectiveRel()}
361
+ target={local.target}
362
+ >
363
+ {local.children}
364
+ </a>
365
+ </Show>
366
+ ) as JSX.Element;
367
+ }