@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.
- package/LICENSE +21 -0
- package/README.md +11 -0
- package/package.json +146 -0
- package/scripts/download-fonts.ts +136 -0
- package/scripts/populate-fonts.ts +721 -0
- package/src/await.ts +2 -0
- package/src/boundaries/index.ts +99 -0
- package/src/broadcast/channel.ts +65 -0
- package/src/broadcast/context.ts +12 -0
- package/src/broadcast/index.ts +75 -0
- package/src/broadcast/provider.tsx +7 -0
- package/src/build-location.ts +2 -0
- package/src/caches/index.ts +384 -0
- package/src/client/index.ts +75 -0
- package/src/client.tsx +2 -0
- package/src/codegen/index.ts +41 -0
- package/src/components/await.tsx +140 -0
- package/src/components/dev-error-overlay.tsx +154 -0
- package/src/components/direction-script.tsx +22 -0
- package/src/components/index.ts +150 -0
- package/src/components/locale-script.tsx +26 -0
- package/src/components/reset-css.tsx +14 -0
- package/src/components/ssr-context.tsx +37 -0
- package/src/components/theme-script.tsx +22 -0
- package/src/components/view-transition-css.tsx +21 -0
- package/src/config/index.ts +84 -0
- package/src/create-client-only-fn.ts +1 -0
- package/src/create-isomorphic-fn.ts +2 -0
- package/src/create-layout.ts +1 -0
- package/src/create-page.ts +1 -0
- package/src/create-path-segment.ts +1 -0
- package/src/create-query-client-getter.ts +2 -0
- package/src/create-root-layout.ts +1 -0
- package/src/create-router.ts +8 -0
- package/src/create-server-only-fn.ts +1 -0
- package/src/dedupe/index.ts +151 -0
- package/src/defer/index.ts +82 -0
- package/src/direction/index.tsx +163 -0
- package/src/direction.ts +3 -0
- package/src/duration/index.ts +18 -0
- package/src/env-fn/index.ts +101 -0
- package/src/env-fn.ts +2 -0
- package/src/errors/index.ts +166 -0
- package/src/fonts/abeezee.ts +50 -0
- package/src/fonts/abel.ts +26 -0
- package/src/fonts/abril-fatface.ts +34 -0
- package/src/fonts/acme.ts +26 -0
- package/src/fonts/advent-pro.ts +92 -0
- package/src/fonts/alegreya-sans.ts +228 -0
- package/src/fonts/alegreya.ts +228 -0
- package/src/fonts/alex-brush.ts +42 -0
- package/src/fonts/alfa-slab-one.ts +42 -0
- package/src/fonts/alice.ts +48 -0
- package/src/fonts/amatic-sc.ts +94 -0
- package/src/fonts/amiri.ts +114 -0
- package/src/fonts/antic-slab.ts +26 -0
- package/src/fonts/anton.ts +42 -0
- package/src/fonts/archivo-black.ts +34 -0
- package/src/fonts/archivo-narrow.ts +114 -0
- package/src/fonts/archivo.ts +66 -0
- package/src/fonts/arimo.ts +256 -0
- package/src/fonts/arvo.ts +50 -0
- package/src/fonts/asap.ts +66 -0
- package/src/fonts/assistant.ts +64 -0
- package/src/fonts/baloo-2.ts +82 -0
- package/src/fonts/barlow-condensed.ts +114 -0
- package/src/fonts/barlow-semi-condensed.ts +114 -0
- package/src/fonts/barlow.ts +114 -0
- package/src/fonts/be-vietnam-pro.ts +114 -0
- package/src/fonts/bebas-neue.ts +34 -0
- package/src/fonts/besley.ts +82 -0
- package/src/fonts/biorhyme.ts +50 -0
- package/src/fonts/bitter.ts +94 -0
- package/src/fonts/black-ops-one.ts +49 -0
- package/src/fonts/bree-serif.ts +34 -0
- package/src/fonts/bricolage-grotesque.ts +66 -0
- package/src/fonts/cabin.ts +114 -0
- package/src/fonts/cairo.ts +66 -0
- package/src/fonts/cardo.ts +193 -0
- package/src/fonts/catamaran.ts +41 -0
- package/src/fonts/caveat.ts +78 -0
- package/src/fonts/chakra-petch.ts +142 -0
- package/src/fonts/chivo.ts +66 -0
- package/src/fonts/cinzel.ts +50 -0
- package/src/fonts/comfortaa.ts +109 -0
- package/src/fonts/commissioner.ts +64 -0
- package/src/fonts/component.tsx +27 -0
- package/src/fonts/cormorant-garamond.ts +171 -0
- package/src/fonts/cormorant.ts +170 -0
- package/src/fonts/courgette.ts +34 -0
- package/src/fonts/courier-prime.ts +82 -0
- package/src/fonts/create-font.ts +47 -0
- package/src/fonts/create-registry-font.ts +40 -0
- package/src/fonts/crete-round.ts +50 -0
- package/src/fonts/crimson-text.ts +114 -0
- package/src/fonts/css.ts +86 -0
- package/src/fonts/dancing-script.ts +66 -0
- package/src/fonts/dm-mono.ts +50 -0
- package/src/fonts/dm-sans.ts +50 -0
- package/src/fonts/dm-serif-display.ts +50 -0
- package/src/fonts/domine.ts +50 -0
- package/src/fonts/dosis.ts +66 -0
- package/src/fonts/eb-garamond.ts +228 -0
- package/src/fonts/eczar.ts +94 -0
- package/src/fonts/encode-sans.ts +42 -0
- package/src/fonts/epilogue.ts +66 -0
- package/src/fonts/exo-2.ts +94 -0
- package/src/fonts/exo.ts +66 -0
- package/src/fonts/figtree.ts +82 -0
- package/src/fonts/fira-code.ts +121 -0
- package/src/fonts/fira-sans-condensed.ts +228 -0
- package/src/fonts/fira-sans.ts +228 -0
- package/src/fonts/fjalla-one.ts +49 -0
- package/src/fonts/font-urls.gen.json +1962 -0
- package/src/fonts/fraunces.ts +66 -0
- package/src/fonts/fredoka.ts +64 -0
- package/src/fonts/gelasio.ts +114 -0
- package/src/fonts/gloria-hallelujah.ts +34 -0
- package/src/fonts/gothic-a1.ts +124 -0
- package/src/fonts/great-vibes.ts +64 -0
- package/src/fonts/gudea.ts +66 -0
- package/src/fonts/heebo.ts +57 -0
- package/src/fonts/hind-siliguri.ts +66 -0
- package/src/fonts/hind.ts +66 -0
- package/src/fonts/ibm-plex-mono.ts +172 -0
- package/src/fonts/ibm-plex-sans-arabic.ts +80 -0
- package/src/fonts/ibm-plex-sans.ts +199 -0
- package/src/fonts/ibm-plex-serif.ts +171 -0
- package/src/fonts/inconsolata.ts +66 -0
- package/src/fonts/index.ts +4 -0
- package/src/fonts/indie-flower.ts +34 -0
- package/src/fonts/instrument-sans.ts +82 -0
- package/src/fonts/instrument-serif.ts +50 -0
- package/src/fonts/inter-display.ts +52 -0
- package/src/fonts/inter-tight.ts +124 -0
- package/src/fonts/inter.ts +123 -0
- package/src/fonts/jetbrains-mono.ts +199 -0
- package/src/fonts/josefin-sans.ts +114 -0
- package/src/fonts/josefin-slab.ts +50 -0
- package/src/fonts/jost.ts +64 -0
- package/src/fonts/kalam.ts +66 -0
- package/src/fonts/kanit.ts +142 -0
- package/src/fonts/karla.ts +82 -0
- package/src/fonts/khand.ts +66 -0
- package/src/fonts/lato.ts +82 -0
- package/src/fonts/league-spartan.ts +42 -0
- package/src/fonts/lexend-deca.ts +42 -0
- package/src/fonts/lexend.ts +42 -0
- package/src/fonts/libre-baskerville.ts +82 -0
- package/src/fonts/libre-franklin.ts +95 -0
- package/src/fonts/lilita-one.ts +34 -0
- package/src/fonts/lobster-two.ts +50 -0
- package/src/fonts/lobster.ts +56 -0
- package/src/fonts/lora.ts +235 -0
- package/src/fonts/m-plus-rounded-1c.ts +138 -0
- package/src/fonts/manrope.ts +109 -0
- package/src/fonts/maven-pro.ts +66 -0
- package/src/fonts/merriweather-sans.ts +142 -0
- package/src/fonts/merriweather.ts +171 -0
- package/src/fonts/montserrat-alternates.ts +171 -0
- package/src/fonts/montserrat.ts +94 -0
- package/src/fonts/mukta.ts +66 -0
- package/src/fonts/mulish.ts +170 -0
- package/src/fonts/nanum-gothic.ts +34 -0
- package/src/fonts/nanum-myeongjo.ts +34 -0
- package/src/fonts/neuton.ts +66 -0
- package/src/fonts/newsreader.ts +114 -0
- package/src/fonts/noticia-text.ts +114 -0
- package/src/fonts/noto-sans-arabic.ts +58 -0
- package/src/fonts/noto-sans-hk.ts +49 -0
- package/src/fonts/noto-sans-jp.ts +49 -0
- package/src/fonts/noto-sans-kr.ts +49 -0
- package/src/fonts/noto-sans-sc.ts +49 -0
- package/src/fonts/noto-sans-tc.ts +49 -0
- package/src/fonts/noto-sans.ts +140 -0
- package/src/fonts/noto-serif-jp.ts +80 -0
- package/src/fonts/noto-serif.ts +140 -0
- package/src/fonts/nunito-sans.ts +170 -0
- package/src/fonts/nunito.ts +170 -0
- package/src/fonts/old-standard-tt.ts +133 -0
- package/src/fonts/onest.ts +48 -0
- package/src/fonts/open-sans.ts +340 -0
- package/src/fonts/orbitron.ts +34 -0
- package/src/fonts/oswald.ts +94 -0
- package/src/fonts/outfit.ts +34 -0
- package/src/fonts/overpass.ts +94 -0
- package/src/fonts/oxygen.ts +50 -0
- package/src/fonts/pacifico.ts +56 -0
- package/src/fonts/passion-one.ts +50 -0
- package/src/fonts/pathway-gothic-one.ts +34 -0
- package/src/fonts/permanent-marker.ts +26 -0
- package/src/fonts/philosopher.ts +172 -0
- package/src/fonts/play.ts +109 -0
- package/src/fonts/playfair-display.ts +142 -0
- package/src/fonts/plus-jakarta-sans.ts +142 -0
- package/src/fonts/poppins.ts +114 -0
- package/src/fonts/prompt.ts +142 -0
- package/src/fonts/pt-sans-narrow.ts +78 -0
- package/src/fonts/pt-sans.ts +138 -0
- package/src/fonts/pt-serif.ts +138 -0
- package/src/fonts/public-sans.ts +66 -0
- package/src/fonts/questrial.ts +42 -0
- package/src/fonts/quicksand.ts +66 -0
- package/src/fonts/rajdhani.ts +66 -0
- package/src/fonts/raleway.ts +94 -0
- package/src/fonts/recursive.ts +80 -0
- package/src/fonts/red-hat-display.ts +82 -0
- package/src/fonts/red-rose.ts +66 -0
- package/src/fonts/registry.gen.ts +173 -0
- package/src/fonts/righteous.ts +34 -0
- package/src/fonts/roboto-condensed.ts +124 -0
- package/src/fonts/roboto-flex.ts +64 -0
- package/src/fonts/roboto-mono.ts +199 -0
- package/src/fonts/roboto-slab.ts +72 -0
- package/src/fonts/roboto.ts +156 -0
- package/src/fonts/rokkitt.ts +66 -0
- package/src/fonts/rubik.ts +199 -0
- package/src/fonts/sacramento.ts +34 -0
- package/src/fonts/saira.ts +66 -0
- package/src/fonts/sarabun.ts +142 -0
- package/src/fonts/satisfy.ts +26 -0
- package/src/fonts/sawarabi-mincho.ts +41 -0
- package/src/fonts/shadows-into-light.ts +34 -0
- package/src/fonts/signika-negative.ts +66 -0
- package/src/fonts/signika.ts +66 -0
- package/src/fonts/slabo-27px.ts +34 -0
- package/src/fonts/sora.ts +50 -0
- package/src/fonts/source-code-pro.ts +228 -0
- package/src/fonts/source-sans-3.ts +228 -0
- package/src/fonts/source-serif-4.ts +199 -0
- package/src/fonts/space-grotesk.ts +66 -0
- package/src/fonts/space-mono.ts +114 -0
- package/src/fonts/spectral.ts +170 -0
- package/src/fonts/tajawal.ts +50 -0
- package/src/fonts/teko.ts +66 -0
- package/src/fonts/tenor-sans.ts +41 -0
- package/src/fonts/tinos.ts +256 -0
- package/src/fonts/titillium-web.ts +82 -0
- package/src/fonts/types.ts +43 -0
- package/src/fonts/ubuntu-condensed.ts +63 -0
- package/src/fonts/ubuntu-mono.ts +195 -0
- package/src/fonts/ubuntu.ts +195 -0
- package/src/fonts/unbounded.ts +94 -0
- package/src/fonts/unicode-ranges.gen.ts +28 -0
- package/src/fonts/urbanist.ts +50 -0
- package/src/fonts/varela-round.ts +49 -0
- package/src/fonts/vollkorn.ts +199 -0
- package/src/fonts/work-sans.ts +66 -0
- package/src/fonts/yanone-kaffeesatz.ts +128 -0
- package/src/fonts/yantramanav.ts +66 -0
- package/src/fonts/yellowtail.ts +34 -0
- package/src/fonts/young-serif.ts +34 -0
- package/src/fonts/zen-kaku-gothic-new.ts +64 -0
- package/src/fonts/zilla-slab.ts +82 -0
- package/src/form/index.tsx +205 -0
- package/src/generators/index.ts +1589 -0
- package/src/head-client/index.ts +649 -0
- package/src/history/index.ts +210 -0
- package/src/hooks.ts +61 -0
- package/src/hydrate/index.tsx +381 -0
- package/src/hydration/index.ts +187 -0
- package/src/i18n/index.ts +250 -0
- package/src/i18n/jsx.tsx +42 -0
- package/src/i18n/message-format.ts +174 -0
- package/src/image/index.tsx +473 -0
- package/src/image.ts +12 -0
- package/src/intercept-outlet/index.tsx +18 -0
- package/src/intercept-outlet.ts +2 -0
- package/src/internal/index.ts +143 -0
- package/src/internal/once-idle.ts +24 -0
- package/src/internal.ts +12 -0
- package/src/lazy/index.tsx +205 -0
- package/src/link/index.tsx +367 -0
- package/src/loader-pipeline/index.ts +639 -0
- package/src/locale/index.tsx +137 -0
- package/src/locale.ts +3 -0
- package/src/logger.ts +22 -0
- package/src/middleware/builtins/api-proxy.ts +64 -0
- package/src/middleware/builtins/cdn-proxy.ts +58 -0
- package/src/middleware/builtins/i18n.ts +256 -0
- package/src/middleware/builtins/keepalive.ts +24 -0
- package/src/middleware/builtins/markdown-negotiation.ts +141 -0
- package/src/middleware/builtins/static-assets.ts +32 -0
- package/src/middleware/index.ts +223 -0
- package/src/module-graph/index.ts +181 -0
- package/src/mount/index.ts +146 -0
- package/src/navigate.ts +1 -0
- package/src/navigation/index.ts +1278 -0
- package/src/navigation/types.ts +59 -0
- package/src/navigation-progress/index.tsx +247 -0
- package/src/navigation-progress.ts +1 -0
- package/src/ndjson-client/index.ts +376 -0
- package/src/ndjson-server/index.ts +284 -0
- package/src/outlet/index.tsx +633 -0
- package/src/outlet/types.ts +243 -0
- package/src/plugins/css-transform.ts +70 -0
- package/src/plugins/dev-cdn-cache.ts +454 -0
- package/src/plugins/dev-dashboard/client.tsx +2603 -0
- package/src/plugins/dev-dashboard/match-route-tree.ts +159 -0
- package/src/plugins/dev-dashboard/plugin.ts +404 -0
- package/src/plugins/dev-prerender.ts +168 -0
- package/src/plugins/dev-server.ts +327 -0
- package/src/plugins/generate-watch.ts +32 -0
- package/src/plugins/image-plugin.ts +171 -0
- package/src/plugins/index.ts +445 -0
- package/src/plugins/options.ts +37 -0
- package/src/plugins/parse-utils.ts +59 -0
- package/src/plugins/prerender-plugin.ts +144 -0
- package/src/plugins/purge.ts +242 -0
- package/src/plugins/server-fn.ts +641 -0
- package/src/plugins/service-worker.ts +225 -0
- package/src/plugins/solid-jsx.d.ts +12 -0
- package/src/plugins/sx-ast/atomic.ts +166 -0
- package/src/plugins/sx-ast/evaluator.ts +235 -0
- package/src/plugins/sx-ast/index.ts +372 -0
- package/src/plugins/sx-ast/parser.ts +22 -0
- package/src/plugins/sx-ast/rewrite.ts +763 -0
- package/src/plugins/sx.d.ts +14 -0
- package/src/plugins/tw-compile.ts +240 -0
- package/src/plugins/types.ts +41 -0
- package/src/plugins/virtual.d.ts +110 -0
- package/src/plugins/virtual.ts +184 -0
- package/src/preload/index.ts +77 -0
- package/src/prerender/index.ts +431 -0
- package/src/query-client/index.tsx +168 -0
- package/src/registry/index.ts +88 -0
- package/src/reset-css.ts +1 -0
- package/src/revalidation/index.ts +84 -0
- package/src/rewrite/index.ts +83 -0
- package/src/route-builder/create-layout.ts +655 -0
- package/src/route-builder/create-page.ts +806 -0
- package/src/route-builder/create-path-segment.ts +34 -0
- package/src/route-builder/create-root-layout.ts +608 -0
- package/src/route-builder/index.ts +49 -0
- package/src/route-builder/register.ts +242 -0
- package/src/route-builder/types.ts +294 -0
- package/src/router-config/index.ts +91 -0
- package/src/router-primitives/index.ts +25 -0
- package/src/router-primitives/location.ts +29 -0
- package/src/router-primitives/match-id.ts +111 -0
- package/src/router-primitives/paths.ts +109 -0
- package/src/router-primitives/tree.ts +243 -0
- package/src/router-primitives/types.ts +74 -0
- package/src/router.ts +31 -0
- package/src/search-engine/bing.ts +32 -0
- package/src/search-engine/google-jwt.ts +95 -0
- package/src/search-engine/google.ts +139 -0
- package/src/search-engine/index-now.ts +64 -0
- package/src/search-engine/index.ts +12 -0
- package/src/security/index.ts +230 -0
- package/src/server/index.ts +286 -0
- package/src/server-context/browser.ts +113 -0
- package/src/server-context/index.ts +217 -0
- package/src/server-fn/index.ts +710 -0
- package/src/server-fn-query.ts +169 -0
- package/src/server-handler/etag.ts +51 -0
- package/src/server-handler/headers.ts +5 -0
- package/src/server-handler/index.ts +1664 -0
- package/src/server-handler/types.ts +24 -0
- package/src/server-handler/validate-static-params.ts +130 -0
- package/src/server.ts +2 -0
- package/src/service-worker/template.ts +153 -0
- package/src/service-worker-hook/index.ts +70 -0
- package/src/sitemap/index.ts +231 -0
- package/src/ssr/critical-css.ts +150 -0
- package/src/ssr/head-prefix.ts +65 -0
- package/src/ssr/head.ts +259 -0
- package/src/ssr/index.tsx +879 -0
- package/src/state-parser/index.ts +266 -0
- package/src/store/filesystem.ts +131 -0
- package/src/store/index.ts +29 -0
- package/src/styles/index.ts +1200 -0
- package/src/styles/sx-types.ts +11 -0
- package/src/testing/index.ts +251 -0
- package/src/testing.ts +2 -0
- package/src/theme/index.tsx +232 -0
- package/src/theme.ts +3 -0
- package/src/tracing/index.ts +4 -0
- package/src/tracing/noop.ts +13 -0
- package/src/tracing/otel.ts +37 -0
- package/src/tracing/timing.ts +34 -0
- package/src/tracing/types.ts +9 -0
- package/src/url/index.ts +140 -0
- package/src/use-router.ts +11 -0
- package/src/use-suspense-query.ts +2 -0
- package/src/validation/index.ts +57 -0
- package/src/view-transition-css.ts +1 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import { createMemo, createSignal, type JSX, splitProps } from "solid-js";
|
|
2
|
+
import { warn } from "../logger.ts";
|
|
3
|
+
|
|
4
|
+
/* ── Static image data ── */
|
|
5
|
+
|
|
6
|
+
export interface StaticImageData {
|
|
7
|
+
blurDataURL: string;
|
|
8
|
+
height: number;
|
|
9
|
+
src: string;
|
|
10
|
+
variants: Record<number, string>;
|
|
11
|
+
width: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isStaticImage(src: StaticImageData | string | null | undefined): src is StaticImageData {
|
|
15
|
+
return src !== null && src !== undefined && typeof src === "object" && "variants" in src;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* ── Loader types ── */
|
|
19
|
+
|
|
20
|
+
export interface ImageLoaderParams {
|
|
21
|
+
quality: number;
|
|
22
|
+
src: string;
|
|
23
|
+
width: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type ImageLoader = (params: ImageLoaderParams) => string;
|
|
27
|
+
|
|
28
|
+
export interface ImageConfig {
|
|
29
|
+
loader?: ImageLoader;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* ── Props ── */
|
|
33
|
+
|
|
34
|
+
type FlareImgProps = Omit<
|
|
35
|
+
JSX.ImgHTMLAttributes<HTMLImageElement>,
|
|
36
|
+
"alt" | "decoding" | "fetchpriority" | "height" | "loading" | "src" | "srcset" | "width"
|
|
37
|
+
>;
|
|
38
|
+
|
|
39
|
+
interface ImagePropsBase extends FlareImgProps {
|
|
40
|
+
alt: string;
|
|
41
|
+
blurDataURL?: string;
|
|
42
|
+
loader?: ImageLoader;
|
|
43
|
+
placeholder?: "blur" | "none";
|
|
44
|
+
priority?: boolean;
|
|
45
|
+
quality?: number;
|
|
46
|
+
sizes?: string;
|
|
47
|
+
src: StaticImageData | string;
|
|
48
|
+
widths?: number[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ResponsiveImageProps extends ImagePropsBase {
|
|
52
|
+
aspectRatio?: number;
|
|
53
|
+
fill?: never;
|
|
54
|
+
fixed?: never;
|
|
55
|
+
height?: never;
|
|
56
|
+
maxHeight?: number;
|
|
57
|
+
maxWidth: number;
|
|
58
|
+
width?: never;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface FixedImageProps extends ImagePropsBase {
|
|
62
|
+
aspectRatio?: never;
|
|
63
|
+
fill?: never;
|
|
64
|
+
fixed: true;
|
|
65
|
+
height: number;
|
|
66
|
+
maxHeight?: never;
|
|
67
|
+
maxWidth?: never;
|
|
68
|
+
width: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface FillImageProps extends ImagePropsBase {
|
|
72
|
+
aspectRatio?: number;
|
|
73
|
+
fill: true;
|
|
74
|
+
fixed?: never;
|
|
75
|
+
height?: never;
|
|
76
|
+
maxHeight?: never;
|
|
77
|
+
maxWidth?: never;
|
|
78
|
+
width?: never;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Static import — dimensions/blur auto-derived, maxWidth optional */
|
|
82
|
+
export interface StaticResponsiveImageProps extends FlareImgProps {
|
|
83
|
+
alt: string;
|
|
84
|
+
blurDataURL?: string;
|
|
85
|
+
fill?: never;
|
|
86
|
+
fixed?: never;
|
|
87
|
+
height?: never;
|
|
88
|
+
loader?: ImageLoader;
|
|
89
|
+
maxHeight?: number;
|
|
90
|
+
maxWidth?: number;
|
|
91
|
+
placeholder?: "blur" | "none";
|
|
92
|
+
priority?: boolean;
|
|
93
|
+
quality?: number;
|
|
94
|
+
sizes?: string;
|
|
95
|
+
src: StaticImageData;
|
|
96
|
+
width?: never;
|
|
97
|
+
widths?: number[];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export type ImageProps = FillImageProps | FixedImageProps | ResponsiveImageProps | StaticResponsiveImageProps;
|
|
101
|
+
|
|
102
|
+
/* Flattened internal type for splitProps — avoids TS intersection issues with Omit */
|
|
103
|
+
interface ImagePropsInternal {
|
|
104
|
+
alt: string;
|
|
105
|
+
aspectRatio?: number;
|
|
106
|
+
blurDataURL?: string;
|
|
107
|
+
fill?: boolean;
|
|
108
|
+
fixed?: boolean;
|
|
109
|
+
height?: number;
|
|
110
|
+
loader?: ImageLoader;
|
|
111
|
+
maxHeight?: number;
|
|
112
|
+
maxWidth?: number;
|
|
113
|
+
onLoad?: JSX.EventHandlerUnion<HTMLImageElement, Event>;
|
|
114
|
+
placeholder?: "blur" | "none";
|
|
115
|
+
priority?: boolean;
|
|
116
|
+
quality?: number;
|
|
117
|
+
sizes?: string;
|
|
118
|
+
src: StaticImageData | string;
|
|
119
|
+
style?: JSX.CSSProperties | string;
|
|
120
|
+
width?: number;
|
|
121
|
+
widths?: number[];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* ── Global config ── */
|
|
125
|
+
|
|
126
|
+
let globalConfig: ImageConfig = {};
|
|
127
|
+
|
|
128
|
+
export function configureImage(config: ImageConfig): void {
|
|
129
|
+
globalConfig = config;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** @internal */
|
|
133
|
+
export function resetImageConfig(): void {
|
|
134
|
+
globalConfig = {};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* ── Constants ── */
|
|
138
|
+
|
|
139
|
+
const DEFAULT_QUALITY = 75;
|
|
140
|
+
const DEFAULT_WIDTHS = [640, 750, 828, 1080, 1200, 1920, 2048, 3840];
|
|
141
|
+
|
|
142
|
+
/* ── generateSrcSet ── */
|
|
143
|
+
|
|
144
|
+
export function generateSrcSet(params: {
|
|
145
|
+
baseWidth: number;
|
|
146
|
+
loader: ImageLoader;
|
|
147
|
+
maxWidth?: number;
|
|
148
|
+
mode: "density" | "width";
|
|
149
|
+
quality: number;
|
|
150
|
+
src: string;
|
|
151
|
+
widths: number[] | undefined;
|
|
152
|
+
}): string | undefined {
|
|
153
|
+
if (params.mode === "width") {
|
|
154
|
+
const breakpoints = params.widths ?? DEFAULT_WIDTHS;
|
|
155
|
+
const cap = params.maxWidth !== undefined ? params.maxWidth * 2 : Number.POSITIVE_INFINITY;
|
|
156
|
+
const entries: number[] = breakpoints.filter((w) => w > 0 && w <= cap);
|
|
157
|
+
if (params.baseWidth > 0 && !entries.includes(params.baseWidth)) entries.push(params.baseWidth);
|
|
158
|
+
if (entries.length === 0) return undefined;
|
|
159
|
+
entries.sort((a, b) => a - b);
|
|
160
|
+
return entries
|
|
161
|
+
.map((w) => `${params.loader({ quality: params.quality, src: params.src, width: w })} ${w}w`)
|
|
162
|
+
.join(", ");
|
|
163
|
+
}
|
|
164
|
+
const url1x = params.loader({ quality: params.quality, src: params.src, width: params.baseWidth });
|
|
165
|
+
const url2x = params.loader({
|
|
166
|
+
quality: params.quality,
|
|
167
|
+
src: params.src,
|
|
168
|
+
width: params.baseWidth * 2,
|
|
169
|
+
});
|
|
170
|
+
return `${url1x} 1x, ${url2x} 2x`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* ── Static srcset helper ── */
|
|
174
|
+
|
|
175
|
+
export function buildStaticSrcSet(
|
|
176
|
+
variants: Record<number, string>,
|
|
177
|
+
mode: "density" | "width",
|
|
178
|
+
baseWidth: number,
|
|
179
|
+
maxWidth?: number,
|
|
180
|
+
): string | undefined {
|
|
181
|
+
const widths = Object.keys(variants).map(Number);
|
|
182
|
+
if (widths.length === 0) return undefined;
|
|
183
|
+
|
|
184
|
+
if (mode === "density") {
|
|
185
|
+
const url1x = variants[baseWidth];
|
|
186
|
+
const w2x = baseWidth * 2;
|
|
187
|
+
const url2x = variants[w2x] ?? closestVariant(variants, w2x);
|
|
188
|
+
if (!url1x) return undefined;
|
|
189
|
+
return url2x ? `${url1x} 1x, ${url2x} 2x` : `${url1x} 1x`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const cap = maxWidth !== undefined ? maxWidth * 2 : Number.POSITIVE_INFINITY;
|
|
193
|
+
const filtered = widths.filter((w) => w > 0 && w <= cap);
|
|
194
|
+
if (filtered.length === 0) return undefined;
|
|
195
|
+
filtered.sort((a, b) => a - b);
|
|
196
|
+
return filtered.map((w) => `${variants[w]} ${w}w`).join(", ");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function closestVariant(variants: Record<number, string>, target: number): string | undefined {
|
|
200
|
+
const widths = Object.keys(variants)
|
|
201
|
+
.map(Number)
|
|
202
|
+
.sort((a, b) => a - b);
|
|
203
|
+
let best: number | undefined;
|
|
204
|
+
for (const w of widths) {
|
|
205
|
+
if (w >= target) {
|
|
206
|
+
best = w;
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (!best) best = widths[widths.length - 1];
|
|
211
|
+
return best !== undefined ? variants[best] : undefined;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/* ── Pure helpers (not exported) ── */
|
|
215
|
+
|
|
216
|
+
/** Converts a decimal ratio to `W / H` fraction format (jsdom drops bare decimals) */
|
|
217
|
+
function aspectRatioToFraction(ratio: number): string {
|
|
218
|
+
const h = 10000;
|
|
219
|
+
const w = Math.round(ratio * h);
|
|
220
|
+
return `${w} / ${h}`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
type ImageMode = "fill" | "fixed" | "responsive";
|
|
224
|
+
|
|
225
|
+
function computeImageMode(props: ImagePropsInternal): ImageMode {
|
|
226
|
+
if (props.fill) return "fill";
|
|
227
|
+
if (props.fixed) return "fixed";
|
|
228
|
+
return "responsive";
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function computeLayoutStyles(mode: ImageMode, props: ImagePropsInternal): JSX.CSSProperties {
|
|
232
|
+
if (mode === "fixed") {
|
|
233
|
+
return {
|
|
234
|
+
height: `${props.height}px`,
|
|
235
|
+
width: `${props.width}px`,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
if (mode === "fill") {
|
|
239
|
+
const styles: JSX.CSSProperties = {
|
|
240
|
+
height: "100%",
|
|
241
|
+
inset: "0",
|
|
242
|
+
"object-fit": "cover",
|
|
243
|
+
position: "absolute",
|
|
244
|
+
width: "100%",
|
|
245
|
+
};
|
|
246
|
+
if (props.aspectRatio) {
|
|
247
|
+
styles["aspect-ratio"] = aspectRatioToFraction(props.aspectRatio);
|
|
248
|
+
}
|
|
249
|
+
return styles;
|
|
250
|
+
}
|
|
251
|
+
/* responsive */
|
|
252
|
+
const mw = props.maxWidth ?? 0;
|
|
253
|
+
const styles: JSX.CSSProperties = {
|
|
254
|
+
"max-width": `${mw}px`,
|
|
255
|
+
width: "100%",
|
|
256
|
+
};
|
|
257
|
+
if (props.maxHeight) {
|
|
258
|
+
styles["aspect-ratio"] = `${mw} / ${props.maxHeight}`;
|
|
259
|
+
} else if (props.aspectRatio) {
|
|
260
|
+
styles["aspect-ratio"] = aspectRatioToFraction(props.aspectRatio);
|
|
261
|
+
}
|
|
262
|
+
return styles;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function computeSizes(mode: ImageMode, props: ImagePropsInternal): string {
|
|
266
|
+
if (props.sizes) return props.sizes;
|
|
267
|
+
if (mode === "fixed") return `${props.width}px`;
|
|
268
|
+
if (mode === "fill") return "100vw";
|
|
269
|
+
return `(min-width: ${props.maxWidth}px) ${props.maxWidth}px, 100vw`;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function computeBaseWidth(mode: ImageMode, props: ImagePropsInternal): number {
|
|
273
|
+
if (mode === "fixed") return props.width ?? 0;
|
|
274
|
+
if (mode === "responsive") return props.maxWidth ?? 0;
|
|
275
|
+
return DEFAULT_WIDTHS[DEFAULT_WIDTHS.length - 1];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function computeHtmlDimensions(
|
|
279
|
+
mode: ImageMode,
|
|
280
|
+
props: ImagePropsInternal,
|
|
281
|
+
): { height: number | undefined; width: number | undefined } {
|
|
282
|
+
if (mode === "fixed") {
|
|
283
|
+
return { height: props.height, width: props.width };
|
|
284
|
+
}
|
|
285
|
+
if (mode === "fill") {
|
|
286
|
+
return { height: undefined, width: undefined };
|
|
287
|
+
}
|
|
288
|
+
/* responsive */
|
|
289
|
+
const mw = props.maxWidth ?? 0;
|
|
290
|
+
let height: number | undefined;
|
|
291
|
+
if (props.maxHeight) {
|
|
292
|
+
height = props.maxHeight;
|
|
293
|
+
} else if (props.aspectRatio) {
|
|
294
|
+
height = Math.round(mw / props.aspectRatio);
|
|
295
|
+
}
|
|
296
|
+
return { height, width: props.maxWidth };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function cssPropertiesToString(css: JSX.CSSProperties): string {
|
|
300
|
+
return Object.entries(css)
|
|
301
|
+
.map(([key, value]) => `${key}: ${value}`)
|
|
302
|
+
.join("; ");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function mergeStyles(
|
|
306
|
+
layout: JSX.CSSProperties,
|
|
307
|
+
blur: JSX.CSSProperties | undefined,
|
|
308
|
+
user: JSX.CSSProperties | string | undefined,
|
|
309
|
+
): JSX.CSSProperties | string {
|
|
310
|
+
if (!blur && !user) return layout;
|
|
311
|
+
if (!blur && typeof user === "string") {
|
|
312
|
+
return `${cssPropertiesToString(layout)};${user}`;
|
|
313
|
+
}
|
|
314
|
+
if (!blur && typeof user === "object") {
|
|
315
|
+
return { ...layout, ...user };
|
|
316
|
+
}
|
|
317
|
+
if (blur && !user) {
|
|
318
|
+
return { ...layout, ...blur };
|
|
319
|
+
}
|
|
320
|
+
if (typeof user === "string") {
|
|
321
|
+
return `${cssPropertiesToString({ ...layout, ...blur })};${user}`;
|
|
322
|
+
}
|
|
323
|
+
return { ...layout, ...blur, ...user };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/* ── Image component ── */
|
|
327
|
+
|
|
328
|
+
export function Image(props: ImageProps): JSX.Element {
|
|
329
|
+
const [local, rest] = splitProps(props as unknown as ImagePropsInternal, [
|
|
330
|
+
"alt",
|
|
331
|
+
"aspectRatio",
|
|
332
|
+
"blurDataURL",
|
|
333
|
+
"fill",
|
|
334
|
+
"fixed",
|
|
335
|
+
"height",
|
|
336
|
+
"loader",
|
|
337
|
+
"maxHeight",
|
|
338
|
+
"maxWidth",
|
|
339
|
+
"onLoad",
|
|
340
|
+
"placeholder",
|
|
341
|
+
"priority",
|
|
342
|
+
"quality",
|
|
343
|
+
"sizes",
|
|
344
|
+
"src",
|
|
345
|
+
"style",
|
|
346
|
+
"width",
|
|
347
|
+
"widths",
|
|
348
|
+
]);
|
|
349
|
+
|
|
350
|
+
const [loaded, setLoaded] = createSignal(false);
|
|
351
|
+
|
|
352
|
+
const resolvedStatic = createMemo(() => {
|
|
353
|
+
if (!isStaticImage(local.src)) return undefined;
|
|
354
|
+
return local.src;
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
const effectiveLoader = createMemo(() => local.loader ?? globalConfig.loader);
|
|
358
|
+
const quality = createMemo(() => local.quality ?? DEFAULT_QUALITY);
|
|
359
|
+
|
|
360
|
+
const mode = createMemo(() => computeImageMode(local));
|
|
361
|
+
|
|
362
|
+
/* For static images, synthesize an internal props view with auto-derived values */
|
|
363
|
+
const effectiveLocal = createMemo((): ImagePropsInternal => {
|
|
364
|
+
const data = resolvedStatic();
|
|
365
|
+
if (!data) return local;
|
|
366
|
+
return {
|
|
367
|
+
...local,
|
|
368
|
+
blurDataURL: local.blurDataURL ?? data.blurDataURL,
|
|
369
|
+
maxHeight: local.maxHeight ?? data.height,
|
|
370
|
+
maxWidth: local.maxWidth ?? data.width,
|
|
371
|
+
placeholder: local.placeholder ?? "blur",
|
|
372
|
+
src: data.src,
|
|
373
|
+
};
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const dimensions = createMemo(() => computeHtmlDimensions(mode(), effectiveLocal()));
|
|
377
|
+
|
|
378
|
+
const layoutStyles = createMemo(() => {
|
|
379
|
+
const m = mode();
|
|
380
|
+
const eff = effectiveLocal();
|
|
381
|
+
if (m === "responsive" && !eff.maxHeight && !eff.aspectRatio) {
|
|
382
|
+
warn("image", "responsive Image without maxHeight or aspectRatio — no layout shift prevention");
|
|
383
|
+
}
|
|
384
|
+
return computeLayoutStyles(m, eff);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
const resolvedSizes = createMemo(() => computeSizes(mode(), effectiveLocal()));
|
|
388
|
+
|
|
389
|
+
const baseWidth = createMemo(() => computeBaseWidth(mode(), effectiveLocal()));
|
|
390
|
+
|
|
391
|
+
const resolvedSrc = createMemo(() => {
|
|
392
|
+
const data = resolvedStatic();
|
|
393
|
+
if (data) return data.src;
|
|
394
|
+
const loader = effectiveLoader();
|
|
395
|
+
const strSrc = local.src as string;
|
|
396
|
+
if (!loader) return strSrc;
|
|
397
|
+
return loader({ quality: quality(), src: strSrc, width: baseWidth() });
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
const srcset = createMemo(() => {
|
|
401
|
+
const data = resolvedStatic();
|
|
402
|
+
if (data) {
|
|
403
|
+
const m = mode();
|
|
404
|
+
const srcSetMode: "density" | "width" = m === "fixed" ? "density" : "width";
|
|
405
|
+
const bw = baseWidth();
|
|
406
|
+
const maxW = m === "responsive" ? (effectiveLocal().maxWidth ?? data.width) : undefined;
|
|
407
|
+
return buildStaticSrcSet(data.variants, srcSetMode, bw, maxW);
|
|
408
|
+
}
|
|
409
|
+
const loader = effectiveLoader();
|
|
410
|
+
if (!loader) return undefined;
|
|
411
|
+
const m = mode();
|
|
412
|
+
const bw = baseWidth();
|
|
413
|
+
const srcSetMode: "density" | "width" = m === "fixed" ? "density" : "width";
|
|
414
|
+
const maxW = m === "responsive" ? local.maxWidth : undefined;
|
|
415
|
+
return generateSrcSet({
|
|
416
|
+
baseWidth: bw,
|
|
417
|
+
loader,
|
|
418
|
+
maxWidth: maxW,
|
|
419
|
+
mode: srcSetMode,
|
|
420
|
+
quality: quality(),
|
|
421
|
+
src: local.src as string,
|
|
422
|
+
widths: local.widths,
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
const showBlur = createMemo(() => {
|
|
427
|
+
const eff = effectiveLocal();
|
|
428
|
+
if (eff.placeholder !== "blur") return false;
|
|
429
|
+
if (!eff.blurDataURL) {
|
|
430
|
+
warn("image", 'placeholder="blur" requires blurDataURL prop');
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
return !loaded();
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
const blurStyles = createMemo((): JSX.CSSProperties | undefined => {
|
|
437
|
+
if (!showBlur()) return undefined;
|
|
438
|
+
return {
|
|
439
|
+
"background-image": `url("${(effectiveLocal().blurDataURL ?? "").replace(/["\\()]/g, (c) => `\\${c}`)}")`,
|
|
440
|
+
"background-position": "center",
|
|
441
|
+
"background-repeat": "no-repeat",
|
|
442
|
+
"background-size": "cover",
|
|
443
|
+
};
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
const finalStyle = createMemo((): JSX.CSSProperties | string =>
|
|
447
|
+
mergeStyles(layoutStyles(), blurStyles(), local.style),
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
function handleLoad(event: Event): void {
|
|
451
|
+
setLoaded(true);
|
|
452
|
+
if (typeof local.onLoad === "function") {
|
|
453
|
+
(local.onLoad as (e: Event) => void)(event);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return (
|
|
458
|
+
<img
|
|
459
|
+
{...rest}
|
|
460
|
+
alt={local.alt}
|
|
461
|
+
decoding={local.priority ? "sync" : "async"}
|
|
462
|
+
fetchpriority={local.priority ? "high" : undefined}
|
|
463
|
+
height={dimensions().height}
|
|
464
|
+
loading={local.priority ? "eager" : "lazy"}
|
|
465
|
+
onLoad={handleLoad}
|
|
466
|
+
sizes={resolvedSizes()}
|
|
467
|
+
src={resolvedSrc()}
|
|
468
|
+
srcset={srcset()}
|
|
469
|
+
style={finalStyle()}
|
|
470
|
+
width={dimensions().width}
|
|
471
|
+
/>
|
|
472
|
+
) as JSX.Element;
|
|
473
|
+
}
|
package/src/image.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
FillImageProps,
|
|
3
|
+
FixedImageProps,
|
|
4
|
+
ImageConfig,
|
|
5
|
+
ImageLoader,
|
|
6
|
+
ImageLoaderParams,
|
|
7
|
+
ImageProps,
|
|
8
|
+
ResponsiveImageProps,
|
|
9
|
+
StaticImageData,
|
|
10
|
+
StaticResponsiveImageProps,
|
|
11
|
+
} from "./image/index.tsx";
|
|
12
|
+
export { configureImage, Image } from "./image/index.tsx";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type JSX, Show, useContext } from "solid-js";
|
|
2
|
+
import { RouterContext } from "../outlet/index.tsx";
|
|
3
|
+
import type { InterceptedState } from "../outlet/types.ts";
|
|
4
|
+
|
|
5
|
+
export type { InterceptedState } from "../outlet/types.ts";
|
|
6
|
+
|
|
7
|
+
export interface InterceptOutletProps {
|
|
8
|
+
children: (state: InterceptedState) => JSX.Element;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Renders intercepted route content. SSR-safe — returns null when outside
|
|
13
|
+
* FlareProvider (root layout renders in NoHydration during SSR).
|
|
14
|
+
*/
|
|
15
|
+
export function InterceptOutlet(props: InterceptOutletProps): JSX.Element {
|
|
16
|
+
const ctx = useContext(RouterContext);
|
|
17
|
+
return (<Show when={ctx?.intercepted()}>{(state) => props.children(state())}</Show>) as JSX.Element;
|
|
18
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { CustomHeadConfig, HeadConfig, ResponseHeaders } from "../route-builder/types.ts";
|
|
2
|
+
import { BUILDER_MARKER } from "../route-builder/types.ts";
|
|
3
|
+
|
|
4
|
+
// oxlint-disable-next-line typescript/no-explicit-any
|
|
5
|
+
export const EMPTY_OBJ = Object.freeze(Object.create(null)) as Record<string, any>;
|
|
6
|
+
// oxlint-disable-next-line typescript/no-explicit-any
|
|
7
|
+
export const EMPTY_ARR = Object.freeze([]) as readonly any[];
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Filter builder-method boundary fns: if user didn't call .errorRender(fn) etc,
|
|
11
|
+
* the field is a builder method (accepts callback, returns PageResultRender).
|
|
12
|
+
* Real render fns accept a single props arg (length <= 1).
|
|
13
|
+
*/
|
|
14
|
+
export const isRenderFn = (fn: unknown): fn is (...args: unknown[]) => unknown =>
|
|
15
|
+
typeof fn === "function" && fn.length <= 1 && !(BUILDER_MARKER in fn);
|
|
16
|
+
|
|
17
|
+
export function concatArrays<T>(a: T[] | undefined, b: T[] | undefined): T[] | undefined {
|
|
18
|
+
if (a && b) return [...a, ...b];
|
|
19
|
+
return b ?? a;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const UNSAFE_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
23
|
+
|
|
24
|
+
/* scalar fields: child overrides parent */
|
|
25
|
+
const SCALAR_FIELDS = new Set(["canonical", "css", "description", "keywords", "title"]);
|
|
26
|
+
|
|
27
|
+
/* object fields: merge per-key, child wins */
|
|
28
|
+
const OBJECT_FIELDS = new Set(["favicons", "languages", "meta", "openGraph", "robots", "twitter"]);
|
|
29
|
+
|
|
30
|
+
/* array fields: concatenate */
|
|
31
|
+
const ARRAY_FIELDS = new Set(["images", "jsonLd"]);
|
|
32
|
+
|
|
33
|
+
function mergeCustom(parent: CustomHeadConfig | undefined, child: CustomHeadConfig | undefined): CustomHeadConfig {
|
|
34
|
+
if (!parent && !child) return {};
|
|
35
|
+
if (!parent) return { ...child };
|
|
36
|
+
if (!child) return { ...parent };
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
links: concatArrays(parent.links, child.links),
|
|
40
|
+
meta: concatArrays(parent.meta, child.meta),
|
|
41
|
+
scripts: concatArrays(parent.scripts, child.scripts),
|
|
42
|
+
styles: concatArrays(parent.styles, child.styles),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function mergeHeadConfigs(parent: HeadConfig | undefined, child: HeadConfig | undefined): HeadConfig {
|
|
47
|
+
if (!parent && !child) return {};
|
|
48
|
+
if (!parent) return { ...child };
|
|
49
|
+
if (!child) return { ...parent };
|
|
50
|
+
|
|
51
|
+
const result: Record<string, unknown> = { ...parent };
|
|
52
|
+
|
|
53
|
+
for (const key of Object.keys(child)) {
|
|
54
|
+
if (UNSAFE_KEYS.has(key)) continue;
|
|
55
|
+
const childVal = (child as Record<string, unknown>)[key];
|
|
56
|
+
const parentVal = (parent as Record<string, unknown>)[key];
|
|
57
|
+
|
|
58
|
+
if (key === "custom") {
|
|
59
|
+
result.custom = mergeCustom(parentVal as CustomHeadConfig | undefined, childVal as CustomHeadConfig | undefined);
|
|
60
|
+
} else if (SCALAR_FIELDS.has(key)) {
|
|
61
|
+
result[key] = childVal;
|
|
62
|
+
} else if (OBJECT_FIELDS.has(key)) {
|
|
63
|
+
if (parentVal && childVal) {
|
|
64
|
+
result[key] = {
|
|
65
|
+
...(parentVal as Record<string, unknown>),
|
|
66
|
+
...(childVal as Record<string, unknown>),
|
|
67
|
+
};
|
|
68
|
+
} else {
|
|
69
|
+
result[key] = childVal ?? parentVal;
|
|
70
|
+
}
|
|
71
|
+
} else if (ARRAY_FIELDS.has(key)) {
|
|
72
|
+
if (parentVal && childVal) {
|
|
73
|
+
result[key] = [...(parentVal as unknown[]), ...(childVal as unknown[])];
|
|
74
|
+
} else {
|
|
75
|
+
result[key] = childVal ?? parentVal;
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
result[key] = childVal;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return result as HeadConfig;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function mergeResponseHeaders(
|
|
86
|
+
parent: ResponseHeaders | undefined,
|
|
87
|
+
child: ResponseHeaders | undefined,
|
|
88
|
+
): ResponseHeaders {
|
|
89
|
+
if (!parent && !child) return {};
|
|
90
|
+
if (!parent) return { ...child };
|
|
91
|
+
if (!child) return { ...parent };
|
|
92
|
+
const result: ResponseHeaders = { ...parent };
|
|
93
|
+
for (const key of Object.keys(child)) {
|
|
94
|
+
const existing = result[key];
|
|
95
|
+
const value = child[key];
|
|
96
|
+
if (value === undefined) continue;
|
|
97
|
+
if (existing !== undefined && key.toLowerCase() === "set-cookie") {
|
|
98
|
+
const parentVals = Array.isArray(existing) ? existing : [existing];
|
|
99
|
+
const childVals = Array.isArray(value) ? value : [value];
|
|
100
|
+
result[key] = [...parentVals, ...childVals];
|
|
101
|
+
} else {
|
|
102
|
+
result[key] = value;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function applyResponseHeaders(target: Headers, source: ResponseHeaders): void {
|
|
109
|
+
for (const [key, value] of Object.entries(source)) {
|
|
110
|
+
if (Array.isArray(value)) {
|
|
111
|
+
for (const v of value) target.append(key, v);
|
|
112
|
+
} else {
|
|
113
|
+
target.set(key, value);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const IMPORT_MAX_RETRIES = 2;
|
|
119
|
+
const IMPORT_RETRY_DELAY = 200;
|
|
120
|
+
|
|
121
|
+
export function isChunkLoadError(error: unknown): boolean {
|
|
122
|
+
if (!(error instanceof Error)) return false;
|
|
123
|
+
const msg = error.message;
|
|
124
|
+
return (
|
|
125
|
+
msg.includes("Failed to fetch dynamically imported module") ||
|
|
126
|
+
msg.includes("error loading dynamically imported module") ||
|
|
127
|
+
msg.includes("Importing a module script failed") ||
|
|
128
|
+
msg.includes("Loading chunk") ||
|
|
129
|
+
msg.includes("Loading CSS chunk")
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function retryImport<T>(fn: () => Promise<T>, retries = IMPORT_MAX_RETRIES): Promise<T> {
|
|
134
|
+
try {
|
|
135
|
+
return await fn();
|
|
136
|
+
} catch (error) {
|
|
137
|
+
if (retries > 0 && isChunkLoadError(error)) {
|
|
138
|
+
await new Promise((r) => setTimeout(r, IMPORT_RETRY_DELAY));
|
|
139
|
+
return retryImport(fn, retries - 1);
|
|
140
|
+
}
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const EVENTS = ["mousemove", "touchstart", "scroll", "keydown"] as const;
|
|
2
|
+
|
|
3
|
+
export function onceIdle(fn: () => void): void {
|
|
4
|
+
let fired = false;
|
|
5
|
+
|
|
6
|
+
const run = () => {
|
|
7
|
+
if (fired) return;
|
|
8
|
+
fired = true;
|
|
9
|
+
cleanup();
|
|
10
|
+
fn();
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const cleanup = () => {
|
|
14
|
+
for (const e of EVENTS) removeEventListener(e, run, { capture: true });
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
for (const e of EVENTS) {
|
|
18
|
+
addEventListener(e, run, { capture: true, once: true, passive: true });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (typeof requestIdleCallback === "function") {
|
|
22
|
+
requestIdleCallback(run);
|
|
23
|
+
}
|
|
24
|
+
}
|
package/src/internal.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export {
|
|
2
|
+
applyResponseHeaders,
|
|
3
|
+
concatArrays,
|
|
4
|
+
isChunkLoadError,
|
|
5
|
+
isRenderFn,
|
|
6
|
+
mergeHeadConfigs,
|
|
7
|
+
mergeResponseHeaders,
|
|
8
|
+
retryImport,
|
|
9
|
+
} from "./internal/index.ts";
|
|
10
|
+
export { preload } from "./preload/index.ts";
|
|
11
|
+
export type { InferParams, InferSearchParams } from "./route-builder/index.ts";
|
|
12
|
+
export type { RouteData, TreeNode } from "./router-primitives/index.ts";
|