@bleedingdev/modern-js-renderer-solid 0.0.0-stage → 3.9.3-ultramodern.1
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/dist/esm/actions.js +283 -0
- package/dist/esm/application-router.js +25 -0
- package/dist/esm/client.js +49 -0
- package/dist/esm/entry-application.js +60 -0
- package/dist/esm/entry-client.js +23 -0
- package/dist/esm/federation-context.js +25 -0
- package/dist/esm/federation-ssr.js +84 -0
- package/dist/esm/federation.js +83 -0
- package/dist/esm/i18n/I18nProvider.js +33 -0
- package/dist/esm/i18n/LocalizedLink.js +74 -0
- package/dist/esm/i18n/context.js +3 -0
- package/dist/esm/i18n/index.js +8 -0
- package/dist/esm/i18n/languageSync.js +50 -0
- package/dist/esm/i18n/types.js +0 -0
- package/dist/esm/i18n/useI18n.js +74 -0
- package/dist/esm/index.js +58 -0
- package/dist/esm/manifest.js +150 -0
- package/dist/esm/route-completion.js +150 -0
- package/dist/esm/route-data-error.js +108 -0
- package/dist/esm/router-binding/Asset.js +201 -0
- package/dist/esm/router-binding/CatchBoundary.js +94 -0
- package/dist/esm/router-binding/ClientOnly.js +26 -0
- package/dist/esm/router-binding/HeadContent.js +8 -0
- package/dist/esm/router-binding/LICENSE +21 -0
- package/dist/esm/router-binding/Match.js +319 -0
- package/dist/esm/router-binding/Matches.js +117 -0
- package/dist/esm/router-binding/PROVENANCE.json +94 -0
- package/dist/esm/router-binding/RouterProvider.js +51 -0
- package/dist/esm/router-binding/SafeFragment.js +5 -0
- package/dist/esm/router-binding/ScriptOnce.js +15 -0
- package/dist/esm/router-binding/Scripts.js +30 -0
- package/dist/esm/router-binding/ScrollRestoration.js +16 -0
- package/dist/esm/router-binding/Transitioner.js +103 -0
- package/dist/esm/router-binding/awaited.js +48 -0
- package/dist/esm/router-binding/fileRoute.js +83 -0
- package/dist/esm/router-binding/flightData.js +23 -0
- package/dist/esm/router-binding/headContentUtils.js +162 -0
- package/dist/esm/router-binding/history.js +0 -0
- package/dist/esm/router-binding/index.js +35 -0
- package/dist/esm/router-binding/lazyRouteComponent.js +30 -0
- package/dist/esm/router-binding/link.js +389 -0
- package/dist/esm/router-binding/matchContext.js +7 -0
- package/dist/esm/router-binding/nonRouteComponentContext.js +13 -0
- package/dist/esm/router-binding/not-found.js +35 -0
- package/dist/esm/router-binding/publicMatchData.js +447 -0
- package/dist/esm/router-binding/registryTransfer.js +210 -0
- package/dist/esm/router-binding/renderRouteNotFound.js +23 -0
- package/dist/esm/router-binding/route.js +143 -0
- package/dist/esm/router-binding/router.js +13 -0
- package/dist/esm/router-binding/routerContext.js +3 -0
- package/dist/esm/router-binding/routerStores.js +50 -0
- package/dist/esm/router-binding/scroll-restoration.js +13 -0
- package/dist/esm/router-binding/typePrimitives.js +0 -0
- package/dist/esm/router-binding/useBlocker.js +152 -0
- package/dist/esm/router-binding/useCanGoBack.js +7 -0
- package/dist/esm/router-binding/useLoaderData.js +9 -0
- package/dist/esm/router-binding/useLoaderDeps.js +8 -0
- package/dist/esm/router-binding/useLocation.js +14 -0
- package/dist/esm/router-binding/useMatch.js +28 -0
- package/dist/esm/router-binding/useNavigate.js +19 -0
- package/dist/esm/router-binding/useParams.js +13 -0
- package/dist/esm/router-binding/useRouteContext.js +8 -0
- package/dist/esm/router-binding/useRouter.js +10 -0
- package/dist/esm/router-binding/useRouterState.js +19 -0
- package/dist/esm/router-binding/useSearch.js +13 -0
- package/dist/esm/router-binding/utils.js +23 -0
- package/dist/esm/router.js +187 -0
- package/dist/esm-node/actions.js +263 -0
- package/dist/esm-node/application-router.js +25 -0
- package/dist/esm-node/client.js +49 -0
- package/dist/esm-node/entry-application.js +110 -0
- package/dist/esm-node/entry-client.js +23 -0
- package/dist/esm-node/entry-server.js +47 -0
- package/dist/esm-node/federation-context.js +25 -0
- package/dist/esm-node/federation-ssr.js +84 -0
- package/dist/esm-node/federation.js +83 -0
- package/dist/esm-node/i18n/I18nProvider.js +42 -0
- package/dist/esm-node/i18n/LocalizedLink.js +142 -0
- package/dist/esm-node/i18n/context.js +3 -0
- package/dist/esm-node/i18n/index.js +8 -0
- package/dist/esm-node/i18n/languageSync.js +50 -0
- package/dist/esm-node/i18n/types.js +1 -0
- package/dist/esm-node/i18n/useI18n.js +74 -0
- package/dist/esm-node/index.js +58 -0
- package/dist/esm-node/manifest.js +150 -0
- package/dist/esm-node/native-promise-serialization.js +113 -0
- package/dist/esm-node/plugin/compiler.js +209 -0
- package/dist/esm-node/plugin/create.js +235 -0
- package/dist/esm-node/plugin/index.js +73 -0
- package/dist/esm-node/plugin/profile.js +52 -0
- package/dist/esm-node/route-completion.js +160 -0
- package/dist/esm-node/route-data-error.js +108 -0
- package/dist/esm-node/router-binding/Asset.js +171 -0
- package/dist/esm-node/router-binding/CatchBoundary.js +136 -0
- package/dist/esm-node/router-binding/ClientOnly.js +46 -0
- package/dist/esm-node/router-binding/HeadContent.js +8 -0
- package/dist/esm-node/router-binding/LICENSE +21 -0
- package/dist/esm-node/router-binding/Match.js +660 -0
- package/dist/esm-node/router-binding/Matches.js +161 -0
- package/dist/esm-node/router-binding/PROVENANCE.json +94 -0
- package/dist/esm-node/router-binding/RouterProvider.js +87 -0
- package/dist/esm-node/router-binding/SafeFragment.js +5 -0
- package/dist/esm-node/router-binding/ScriptOnce.js +16 -0
- package/dist/esm-node/router-binding/Scripts.js +49 -0
- package/dist/esm-node/router-binding/ScrollRestoration.js +16 -0
- package/dist/esm-node/router-binding/Transitioner.js +103 -0
- package/dist/esm-node/router-binding/awaited.js +75 -0
- package/dist/esm-node/router-binding/fileRoute.js +83 -0
- package/dist/esm-node/router-binding/flightData.js +23 -0
- package/dist/esm-node/router-binding/headContentUtils.js +162 -0
- package/dist/esm-node/router-binding/history.js +1 -0
- package/dist/esm-node/router-binding/index.js +35 -0
- package/dist/esm-node/router-binding/lazyRouteComponent.js +30 -0
- package/dist/esm-node/router-binding/link.js +399 -0
- package/dist/esm-node/router-binding/matchContext.js +7 -0
- package/dist/esm-node/router-binding/nonRouteComponentContext.js +22 -0
- package/dist/esm-node/router-binding/not-found.js +49 -0
- package/dist/esm-node/router-binding/publicMatchData.js +447 -0
- package/dist/esm-node/router-binding/registryTransfer.js +210 -0
- package/dist/esm-node/router-binding/renderRouteNotFound.js +22 -0
- package/dist/esm-node/router-binding/route.js +162 -0
- package/dist/esm-node/router-binding/router.js +13 -0
- package/dist/esm-node/router-binding/routerContext.js +3 -0
- package/dist/esm-node/router-binding/routerStores.js +50 -0
- package/dist/esm-node/router-binding/scroll-restoration.js +12 -0
- package/dist/esm-node/router-binding/typePrimitives.js +1 -0
- package/dist/esm-node/router-binding/useBlocker.js +152 -0
- package/dist/esm-node/router-binding/useCanGoBack.js +7 -0
- package/dist/esm-node/router-binding/useLoaderData.js +9 -0
- package/dist/esm-node/router-binding/useLoaderDeps.js +8 -0
- package/dist/esm-node/router-binding/useLocation.js +14 -0
- package/dist/esm-node/router-binding/useMatch.js +28 -0
- package/dist/esm-node/router-binding/useNavigate.js +19 -0
- package/dist/esm-node/router-binding/useParams.js +13 -0
- package/dist/esm-node/router-binding/useRouteContext.js +8 -0
- package/dist/esm-node/router-binding/useRouter.js +10 -0
- package/dist/esm-node/router-binding/useRouterState.js +19 -0
- package/dist/esm-node/router-binding/useSearch.js +13 -0
- package/dist/esm-node/router-binding/utils.js +23 -0
- package/dist/esm-node/router.js +187 -0
- package/dist/esm-node/server.js +396 -0
- package/dist/types/actions.d.ts +45 -0
- package/dist/types/application-router.d.ts +5 -0
- package/dist/types/client.d.ts +23 -0
- package/dist/types/entry-application.d.ts +10 -0
- package/dist/types/entry-client.d.ts +5 -0
- package/dist/types/entry-server.d.ts +5 -0
- package/dist/types/federation-context.d.ts +16 -0
- package/dist/types/federation-ssr.d.ts +21 -0
- package/dist/types/federation.d.ts +30 -0
- package/dist/types/i18n/I18nProvider.d.ts +21 -0
- package/dist/types/i18n/LocalizedLink.d.ts +43 -0
- package/dist/types/i18n/context.d.ts +11 -0
- package/dist/types/i18n/index.d.ts +9 -0
- package/dist/types/i18n/languageSync.d.ts +31 -0
- package/dist/types/i18n/types.d.ts +33 -0
- package/dist/types/i18n/useI18n.d.ts +24 -0
- package/dist/types/index.d.ts +41 -0
- package/dist/types/manifest.d.ts +55 -0
- package/dist/types/native-promise-serialization.d.ts +9 -0
- package/dist/types/plugin/compiler.d.ts +6 -0
- package/dist/types/plugin/create.d.ts +21 -0
- package/dist/types/plugin/index.d.ts +3 -0
- package/dist/types/plugin/lazy-modules.d.cts +15 -0
- package/dist/types/plugin/preserve-import-loader.d.cts +3 -0
- package/dist/types/plugin/profile.d.ts +2 -0
- package/dist/types/plugin/solid-loader.d.cts +2 -0
- package/dist/types/plugin/svg-component-template.d.cts +6 -0
- package/dist/types/route-completion.d.ts +21 -0
- package/dist/types/route-data-error.d.ts +17 -0
- package/dist/types/router-binding/Asset.d.ts +3 -0
- package/dist/types/router-binding/CatchBoundary.d.ts +13 -0
- package/dist/types/router-binding/ClientOnly.d.ts +31 -0
- package/dist/types/router-binding/HeadContent.d.ts +13 -0
- package/dist/types/router-binding/Match.d.ts +6 -0
- package/dist/types/router-binding/Matches.d.ts +26 -0
- package/dist/types/router-binding/RouterProvider.d.ts +10 -0
- package/dist/types/router-binding/SafeFragment.d.ts +2 -0
- package/dist/types/router-binding/ScriptOnce.d.ts +6 -0
- package/dist/types/router-binding/Scripts.d.ts +2 -0
- package/dist/types/router-binding/ScrollRestoration.d.ts +14 -0
- package/dist/types/router-binding/Transitioner.d.ts +2 -0
- package/dist/types/router-binding/awaited.d.ts +11 -0
- package/dist/types/router-binding/fileRoute.d.ts +54 -0
- package/dist/types/router-binding/flightData.d.ts +38 -0
- package/dist/types/router-binding/headContentUtils.d.ts +15 -0
- package/dist/types/router-binding/history.d.ts +8 -0
- package/dist/types/router-binding/index.d.ts +45 -0
- package/dist/types/router-binding/lazyRouteComponent.d.ts +2 -0
- package/dist/types/router-binding/link.d.ts +43 -0
- package/dist/types/router-binding/matchContext.d.ts +7 -0
- package/dist/types/router-binding/nonRouteComponentContext.d.ts +5 -0
- package/dist/types/router-binding/not-found.d.ts +11 -0
- package/dist/types/router-binding/publicMatchData.d.ts +19 -0
- package/dist/types/router-binding/registryTransfer.d.ts +47 -0
- package/dist/types/router-binding/renderRouteNotFound.d.ts +11 -0
- package/dist/types/router-binding/route.d.ts +105 -0
- package/dist/types/router-binding/router.d.ts +70 -0
- package/dist/types/router-binding/routerContext.d.ts +3 -0
- package/dist/types/router-binding/routerStores.d.ts +2 -0
- package/dist/types/router-binding/scroll-restoration.d.ts +2 -0
- package/dist/types/router-binding/typePrimitives.d.ts +10 -0
- package/dist/types/router-binding/useBlocker.d.ts +70 -0
- package/dist/types/router-binding/useCanGoBack.d.ts +2 -0
- package/dist/types/router-binding/useLoaderData.d.ts +8 -0
- package/dist/types/router-binding/useLoaderDeps.d.ts +8 -0
- package/dist/types/router-binding/useLocation.d.ts +7 -0
- package/dist/types/router-binding/useMatch.d.ts +10 -0
- package/dist/types/router-binding/useNavigate.d.ts +5 -0
- package/dist/types/router-binding/useParams.d.ts +9 -0
- package/dist/types/router-binding/useRouteContext.d.ts +4 -0
- package/dist/types/router-binding/useRouter.d.ts +4 -0
- package/dist/types/router-binding/useRouterState.d.ts +8 -0
- package/dist/types/router-binding/useSearch.d.ts +9 -0
- package/dist/types/router-binding/utils.d.ts +25 -0
- package/dist/types/router.d.ts +31 -0
- package/dist/types/server.d.ts +47 -0
- package/package.json +123 -4
- package/src/actions.tsx +437 -0
- package/src/application-router.tsx +41 -0
- package/src/client.ts +106 -0
- package/src/entry-application.tsx +73 -0
- package/src/entry-client.tsx +48 -0
- package/src/entry-server.tsx +85 -0
- package/src/federation-context.ts +47 -0
- package/src/federation-ssr.ts +117 -0
- package/src/federation.tsx +193 -0
- package/src/i18n/I18nProvider.tsx +54 -0
- package/src/i18n/LocalizedLink.tsx +122 -0
- package/src/i18n/context.ts +13 -0
- package/src/i18n/index.ts +35 -0
- package/src/i18n/languageSync.ts +113 -0
- package/src/i18n/types.ts +35 -0
- package/src/i18n/useI18n.ts +121 -0
- package/src/index.ts +41 -0
- package/src/manifest.ts +337 -0
- package/src/native-promise-serialization.ts +167 -0
- package/src/plugin/compiler.ts +320 -0
- package/src/plugin/create.ts +270 -0
- package/src/plugin/index.ts +72 -0
- package/src/plugin/lazy-modules.cjs +146 -0
- package/src/plugin/preserve-import-loader.cjs +46 -0
- package/src/plugin/profile.ts +42 -0
- package/src/plugin/solid-loader.cjs +83 -0
- package/src/plugin/svg-component-template.cjs +19 -0
- package/src/route-completion.tsx +222 -0
- package/src/route-data-error.ts +175 -0
- package/src/router-binding/Asset.tsx +283 -0
- package/src/router-binding/CatchBoundary.tsx +130 -0
- package/src/router-binding/ClientOnly.tsx +76 -0
- package/src/router-binding/HeadContent.tsx +23 -0
- package/src/router-binding/LICENSE +21 -0
- package/src/router-binding/Match.tsx +397 -0
- package/src/router-binding/Matches.tsx +242 -0
- package/src/router-binding/PROVENANCE.json +94 -0
- package/src/router-binding/RouterProvider.tsx +129 -0
- package/src/router-binding/SafeFragment.tsx +5 -0
- package/src/router-binding/ScriptOnce.tsx +23 -0
- package/src/router-binding/Scripts.tsx +44 -0
- package/src/router-binding/ScrollRestoration.tsx +49 -0
- package/src/router-binding/Transitioner.tsx +172 -0
- package/src/router-binding/awaited.tsx +71 -0
- package/src/router-binding/fileRoute.ts +268 -0
- package/src/router-binding/flightData.ts +90 -0
- package/src/router-binding/headContentUtils.tsx +290 -0
- package/src/router-binding/history.ts +9 -0
- package/src/router-binding/index.tsx +318 -0
- package/src/router-binding/lazyRouteComponent.tsx +70 -0
- package/src/router-binding/link.tsx +865 -0
- package/src/router-binding/matchContext.tsx +15 -0
- package/src/router-binding/nonRouteComponentContext.tsx +23 -0
- package/src/router-binding/not-found.tsx +65 -0
- package/src/router-binding/publicMatchData.ts +674 -0
- package/src/router-binding/registryTransfer.ts +383 -0
- package/src/router-binding/renderRouteNotFound.tsx +53 -0
- package/src/router-binding/route.tsx +665 -0
- package/src/router-binding/router.ts +121 -0
- package/src/router-binding/routerContext.tsx +6 -0
- package/src/router-binding/routerStores.ts +85 -0
- package/src/router-binding/scroll-restoration.tsx +15 -0
- package/src/router-binding/typePrimitives.ts +74 -0
- package/src/router-binding/useBlocker.tsx +359 -0
- package/src/router-binding/useCanGoBack.ts +9 -0
- package/src/router-binding/useLoaderData.tsx +50 -0
- package/src/router-binding/useLoaderDeps.tsx +46 -0
- package/src/router-binding/useLocation.tsx +43 -0
- package/src/router-binding/useMatch.tsx +111 -0
- package/src/router-binding/useNavigate.tsx +43 -0
- package/src/router-binding/useParams.tsx +72 -0
- package/src/router-binding/useRouteContext.ts +30 -0
- package/src/router-binding/useRouter.tsx +17 -0
- package/src/router-binding/useRouterState.tsx +50 -0
- package/src/router-binding/useSearch.tsx +72 -0
- package/src/router-binding/utils.ts +59 -0
- package/src/router.ts +376 -0
- package/src/server.ts +645 -0
- package/README.md +0 -3
|
@@ -0,0 +1,865 @@
|
|
|
1
|
+
import type { ComponentProps, JSX, ValidComponent } from '@solidjs/web';
|
|
2
|
+
import { Dynamic } from '@solidjs/web';
|
|
3
|
+
import type {
|
|
4
|
+
AnyRouter,
|
|
5
|
+
Constrain,
|
|
6
|
+
LinkOptions,
|
|
7
|
+
ParsedLocation,
|
|
8
|
+
RegisteredRouter,
|
|
9
|
+
RoutePaths,
|
|
10
|
+
} from '@tanstack/router-core';
|
|
11
|
+
import {
|
|
12
|
+
deepEqual,
|
|
13
|
+
exactPathTest,
|
|
14
|
+
functionalUpdate,
|
|
15
|
+
getUrlScheme,
|
|
16
|
+
hasKeys,
|
|
17
|
+
isDangerousProtocol,
|
|
18
|
+
preloadWarning,
|
|
19
|
+
removeTrailingSlash,
|
|
20
|
+
} from '@tanstack/router-core';
|
|
21
|
+
import { isServer } from '@tanstack/router-core/isServer';
|
|
22
|
+
import * as Solid from 'solid-js';
|
|
23
|
+
|
|
24
|
+
import { useHydrated } from './ClientOnly';
|
|
25
|
+
import type {
|
|
26
|
+
ValidateLinkOptions,
|
|
27
|
+
ValidateLinkOptionsArray,
|
|
28
|
+
} from './typePrimitives';
|
|
29
|
+
import { useRouter } from './useRouter';
|
|
30
|
+
import { useIntersectionObserver } from './utils';
|
|
31
|
+
|
|
32
|
+
function mergeRefs<T>(...refs: Array<unknown>): (el: T) => void {
|
|
33
|
+
const setRef = (ref: unknown, el: T) => {
|
|
34
|
+
if (typeof ref === 'function') {
|
|
35
|
+
ref(el);
|
|
36
|
+
} else if (Array.isArray(ref)) {
|
|
37
|
+
for (const nestedRef of ref) {
|
|
38
|
+
setRef(nestedRef, el);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (el: T) => {
|
|
44
|
+
for (const ref of refs) {
|
|
45
|
+
setRef(ref, el);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function splitProps<T extends Record<string, any>, TKey extends keyof T>(
|
|
51
|
+
props: T,
|
|
52
|
+
keys: ReadonlyArray<TKey>,
|
|
53
|
+
): [Pick<T, TKey>, Omit<T, TKey>] {
|
|
54
|
+
const _local = {} as Pick<T, TKey>;
|
|
55
|
+
const _rest = {} as Omit<T, TKey>;
|
|
56
|
+
|
|
57
|
+
// A safe way to polyfill splitProps if native getter copy is too complex
|
|
58
|
+
// is just to return [props, Solid.omit(props, keys)] but it modifies typing.
|
|
59
|
+
// Actually, Solid.omit exists!
|
|
60
|
+
// Note: Solid.omit uses rest params (...keys), so we must spread the array.
|
|
61
|
+
return [props as any, Solid.omit(props, ...(keys as any)) as any];
|
|
62
|
+
}
|
|
63
|
+
const timeoutMap = new WeakMap<object, ReturnType<typeof setTimeout>>();
|
|
64
|
+
const cancelPreload = (eventTarget: object) => {
|
|
65
|
+
clearTimeout(timeoutMap.get(eventTarget));
|
|
66
|
+
timeoutMap.delete(eventTarget);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export function useLinkProps<
|
|
70
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
71
|
+
TFrom extends RoutePaths<TRouter['routeTree']> | string = string,
|
|
72
|
+
TTo extends string = '',
|
|
73
|
+
TMaskFrom extends RoutePaths<TRouter['routeTree']> | string = TFrom,
|
|
74
|
+
TMaskTo extends string = '',
|
|
75
|
+
>(
|
|
76
|
+
options: UseLinkPropsOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
|
|
77
|
+
): ComponentProps<'a'> {
|
|
78
|
+
const router = useRouter();
|
|
79
|
+
const shouldHydrateHash = !isServer && !!router.options.ssr;
|
|
80
|
+
const hasHydrated = useHydrated();
|
|
81
|
+
|
|
82
|
+
let hasRenderFetched = false;
|
|
83
|
+
|
|
84
|
+
// Defaults are resolved through accessors at the use sites instead of
|
|
85
|
+
// merging them into the props. Every merge/omit proxy layered here gets
|
|
86
|
+
// re-enumerated by spread() on each navigation, and V8 dispatches proxy
|
|
87
|
+
// traps in native runtime code — keeping this path proxy-free is what
|
|
88
|
+
// keeps Link updates cheap.
|
|
89
|
+
const local = options;
|
|
90
|
+
const activeProps = () => local.activeProps ?? STATIC_ACTIVE_PROPS_GET;
|
|
91
|
+
const inactiveProps = () => local.inactiveProps ?? STATIC_INACTIVE_PROPS_GET;
|
|
92
|
+
|
|
93
|
+
const propsSafeToSpread = Solid.omit(
|
|
94
|
+
options as Record<string, any>,
|
|
95
|
+
'activeProps',
|
|
96
|
+
'inactiveProps',
|
|
97
|
+
'activeOptions',
|
|
98
|
+
'to',
|
|
99
|
+
'preload',
|
|
100
|
+
'preloadDelay',
|
|
101
|
+
'preloadIntentProximity',
|
|
102
|
+
'hashScrollIntoView',
|
|
103
|
+
'replace',
|
|
104
|
+
'startTransition',
|
|
105
|
+
'resetScroll',
|
|
106
|
+
'viewTransition',
|
|
107
|
+
'target',
|
|
108
|
+
'disabled',
|
|
109
|
+
'style',
|
|
110
|
+
'class',
|
|
111
|
+
'onClick',
|
|
112
|
+
'onBlur',
|
|
113
|
+
'onFocus',
|
|
114
|
+
'onMouseEnter',
|
|
115
|
+
'onMouseLeave',
|
|
116
|
+
'onMouseOver',
|
|
117
|
+
'onMouseOut',
|
|
118
|
+
'onTouchStart',
|
|
119
|
+
'ignoreBlocker',
|
|
120
|
+
'params',
|
|
121
|
+
'search',
|
|
122
|
+
'hash',
|
|
123
|
+
'state',
|
|
124
|
+
'mask',
|
|
125
|
+
'reloadDocument',
|
|
126
|
+
'unsafeRelative',
|
|
127
|
+
'from',
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const currentLocation = Solid.createMemo(() => router.stores.location.get(), {
|
|
131
|
+
equals: (prev, next) => prev.href === next.href,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const _options = () => options;
|
|
135
|
+
|
|
136
|
+
const next = Solid.createMemo(
|
|
137
|
+
() => {
|
|
138
|
+
// Rebuild when inherited search/hash or the current route context changes.
|
|
139
|
+
const _fromLocation = currentLocation();
|
|
140
|
+
const options = { _fromLocation, ..._options() } as any;
|
|
141
|
+
// untrack because router-core will also access stores, which are signals in solid
|
|
142
|
+
return Solid.untrack(() => router.buildLocation(options));
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
lazy: true,
|
|
146
|
+
// Navigations usually leave most links' built locations unchanged;
|
|
147
|
+
// comparing hrefs lets downstream memos (href, isActive) skip work.
|
|
148
|
+
equals: (prev, next) =>
|
|
149
|
+
prev.href === next.href &&
|
|
150
|
+
prev.external === next.external &&
|
|
151
|
+
prev.maskedLocation?.href === next.maskedLocation?.href,
|
|
152
|
+
},
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
// A `to` with a URL scheme skips route resolution: the URL itself when the
|
|
156
|
+
// router allows its protocol, `null` when it is blocked, `undefined` for a
|
|
157
|
+
// route path. Read through the accessor so a reactive `to` can move between
|
|
158
|
+
// the three states after setup.
|
|
159
|
+
const directExternalLink = Solid.createMemo(
|
|
160
|
+
() => resolveExternalLink(_options().to, router.protocolAllowlist),
|
|
161
|
+
{ lazy: true },
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// The rendered href. `undefined` means disabled or blocked; neither renders
|
|
165
|
+
// an href, so the browser cannot follow it either.
|
|
166
|
+
const hrefOption = Solid.createMemo(
|
|
167
|
+
() => {
|
|
168
|
+
if (_options().disabled) return undefined;
|
|
169
|
+
const direct = directExternalLink();
|
|
170
|
+
if (direct !== undefined) return direct ?? undefined;
|
|
171
|
+
return getHrefOption(next(), router);
|
|
172
|
+
},
|
|
173
|
+
{ lazy: true },
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const externalLink = Solid.createMemo(
|
|
177
|
+
() => {
|
|
178
|
+
const direct = directExternalLink();
|
|
179
|
+
if (direct !== undefined) return direct ?? undefined;
|
|
180
|
+
const href = hrefOption();
|
|
181
|
+
return href && getUrlScheme(href) ? href : undefined;
|
|
182
|
+
},
|
|
183
|
+
{ lazy: true },
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
// Disabled and blocked links render without an href, do not preload and do
|
|
187
|
+
// not navigate. Enabled external links stay plain anchors.
|
|
188
|
+
const linkDisabled = Solid.createMemo(
|
|
189
|
+
() => !!local.disabled || (!externalLink() && hrefOption() === undefined),
|
|
190
|
+
{ lazy: true },
|
|
191
|
+
);
|
|
192
|
+
const blockedLink = () => linkDisabled() && !local.disabled;
|
|
193
|
+
|
|
194
|
+
const preload = Solid.createMemo(
|
|
195
|
+
() => {
|
|
196
|
+
if (_options().reloadDocument || externalLink() || linkDisabled()) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
return local.preload ?? router.options.defaultPreload;
|
|
200
|
+
},
|
|
201
|
+
{ lazy: true },
|
|
202
|
+
);
|
|
203
|
+
const preloadDelay = () =>
|
|
204
|
+
local.preloadDelay ?? router.options.defaultPreloadDelay ?? 0;
|
|
205
|
+
|
|
206
|
+
const isActive = Solid.createMemo(
|
|
207
|
+
() => {
|
|
208
|
+
if (externalLink() || blockedLink()) return false;
|
|
209
|
+
const activeOptions = local.activeOptions;
|
|
210
|
+
const current = currentLocation();
|
|
211
|
+
const nextLocation = next();
|
|
212
|
+
|
|
213
|
+
if (activeOptions?.exact) {
|
|
214
|
+
const testExact = exactPathTest(
|
|
215
|
+
current.pathname,
|
|
216
|
+
nextLocation.pathname,
|
|
217
|
+
router.basepath,
|
|
218
|
+
);
|
|
219
|
+
if (!testExact) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
} else {
|
|
223
|
+
const currentPath = removeTrailingSlash(
|
|
224
|
+
current.pathname,
|
|
225
|
+
router.basepath,
|
|
226
|
+
);
|
|
227
|
+
const nextPath = removeTrailingSlash(
|
|
228
|
+
nextLocation.pathname,
|
|
229
|
+
router.basepath,
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
const pathIsFuzzyEqual =
|
|
233
|
+
currentPath.startsWith(nextPath) &&
|
|
234
|
+
(currentPath.length === nextPath.length ||
|
|
235
|
+
currentPath[nextPath.length] === '/');
|
|
236
|
+
if (!pathIsFuzzyEqual) {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (activeOptions?.includeSearch ?? true) {
|
|
242
|
+
const searchTest = deepEqual(
|
|
243
|
+
current.search,
|
|
244
|
+
nextLocation.search,
|
|
245
|
+
!activeOptions?.exact,
|
|
246
|
+
activeOptions?.explicitUndefined,
|
|
247
|
+
);
|
|
248
|
+
if (!searchTest) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (activeOptions?.includeHash) {
|
|
254
|
+
const currentHash =
|
|
255
|
+
shouldHydrateHash && !hasHydrated() ? '' : current.hash;
|
|
256
|
+
return currentHash === nextLocation.hash;
|
|
257
|
+
}
|
|
258
|
+
return true;
|
|
259
|
+
},
|
|
260
|
+
{ lazy: true },
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
const doPreload = () =>
|
|
264
|
+
router
|
|
265
|
+
.preloadRoute({ ...options, _builtLocation: next() } as any)
|
|
266
|
+
.catch((err: any) => {
|
|
267
|
+
console.warn(err);
|
|
268
|
+
console.warn(preloadWarning);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const [ref, setRefSignal] = Solid.createSignal<Element | null>(null);
|
|
272
|
+
|
|
273
|
+
const setRef = (el: Element | null) => {
|
|
274
|
+
Solid.runWithOwner(null, () => {
|
|
275
|
+
setRefSignal(el);
|
|
276
|
+
});
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const enqueuePreload = (
|
|
280
|
+
e?: MouseEvent | FocusEvent | IntersectionObserverEntry,
|
|
281
|
+
) => {
|
|
282
|
+
if (!e) {
|
|
283
|
+
cancelPreload(ref);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (
|
|
288
|
+
!(
|
|
289
|
+
(e as IntersectionObserverEntry).isIntersecting ??
|
|
290
|
+
preload() === 'intent'
|
|
291
|
+
)
|
|
292
|
+
) {
|
|
293
|
+
if ((e as IntersectionObserverEntry).isIntersecting === false) {
|
|
294
|
+
cancelPreload(ref);
|
|
295
|
+
}
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (!preloadDelay()) {
|
|
300
|
+
doPreload();
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (!timeoutMap.has(ref)) {
|
|
305
|
+
timeoutMap.set(
|
|
306
|
+
ref,
|
|
307
|
+
setTimeout(() => {
|
|
308
|
+
timeoutMap.delete(ref);
|
|
309
|
+
doPreload();
|
|
310
|
+
}, preloadDelay()),
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
useIntersectionObserver(ref, enqueuePreload, () => preload() !== 'viewport');
|
|
316
|
+
|
|
317
|
+
Solid.createEffect(preload, preloadValue => {
|
|
318
|
+
if (hasRenderFetched) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (preloadValue === 'render') {
|
|
322
|
+
Solid.untrack(() => doPreload());
|
|
323
|
+
hasRenderFetched = true;
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// External and internal links share one props object whose getters follow
|
|
328
|
+
// `externalLink()`, so a reactive `to` can switch between the two kinds.
|
|
329
|
+
|
|
330
|
+
// The click handler
|
|
331
|
+
const handleClick = (e: MouseEvent) => {
|
|
332
|
+
// The browser follows external hrefs; blocked links have none to follow.
|
|
333
|
+
if (externalLink() || linkDisabled()) return;
|
|
334
|
+
const element = e.currentTarget as HTMLAnchorElement | SVGAElement;
|
|
335
|
+
// A download link fetches its resource; the browser handles that click.
|
|
336
|
+
if (element.hasAttribute('download')) return;
|
|
337
|
+
// Check actual element's target attribute as fallback
|
|
338
|
+
const elementTarget = element.getAttribute('target');
|
|
339
|
+
const effectiveTarget = resolvedTarget() ?? elementTarget;
|
|
340
|
+
|
|
341
|
+
if (
|
|
342
|
+
!isCtrlEvent(e) &&
|
|
343
|
+
!e.defaultPrevented &&
|
|
344
|
+
(!effectiveTarget || effectiveTarget === '_self') &&
|
|
345
|
+
e.button === 0
|
|
346
|
+
) {
|
|
347
|
+
e.preventDefault();
|
|
348
|
+
|
|
349
|
+
// All is well? Navigate!
|
|
350
|
+
// N.B. we don't call `router.commitLocation(next) here because we want to run `validateSearch` before committing
|
|
351
|
+
router.navigate({
|
|
352
|
+
...options,
|
|
353
|
+
replace: local.replace,
|
|
354
|
+
resetScroll: local.resetScroll,
|
|
355
|
+
hashScrollIntoView: local.hashScrollIntoView,
|
|
356
|
+
startTransition: local.startTransition,
|
|
357
|
+
viewTransition: local.viewTransition,
|
|
358
|
+
ignoreBlocker: local.ignoreBlocker,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const handleTouchStart = () => {
|
|
364
|
+
if (preload() !== 'intent') return;
|
|
365
|
+
doPreload();
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
const handleLeave = () => {
|
|
369
|
+
if (preload() === 'intent') {
|
|
370
|
+
cancelPreload(ref);
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
const simpleStyling = Solid.createMemo(
|
|
375
|
+
() =>
|
|
376
|
+
activeProps() === STATIC_ACTIVE_PROPS_GET &&
|
|
377
|
+
inactiveProps() === STATIC_INACTIVE_PROPS_GET &&
|
|
378
|
+
local.class === undefined &&
|
|
379
|
+
local.style === undefined,
|
|
380
|
+
{ lazy: true },
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
// activeProps/inactiveProps may supply event handlers too. Each runs after
|
|
384
|
+
// the caller's own and, like it, can prevent the Link's behavior.
|
|
385
|
+
const stateHandler =
|
|
386
|
+
(key: string) => (): JSX.EventHandlerUnion<any, any> | undefined =>
|
|
387
|
+
(resolvedStateProps() as Record<string, any>)[key];
|
|
388
|
+
const handlers = (key: keyof typeof local & string) => [
|
|
389
|
+
() => local[key] as JSX.EventHandlerUnion<any, any> | undefined,
|
|
390
|
+
stateHandler(key),
|
|
391
|
+
];
|
|
392
|
+
const onClick = createComposedHandler(handlers('onClick'), handleClick);
|
|
393
|
+
const onBlur = createComposedHandler(handlers('onBlur'), handleLeave);
|
|
394
|
+
const onFocus = createComposedHandler(handlers('onFocus'), enqueuePreload);
|
|
395
|
+
const onMouseEnter = createComposedHandler(
|
|
396
|
+
handlers('onMouseEnter'),
|
|
397
|
+
enqueuePreload,
|
|
398
|
+
);
|
|
399
|
+
const onMouseOver = createComposedHandler(
|
|
400
|
+
handlers('onMouseOver'),
|
|
401
|
+
enqueuePreload,
|
|
402
|
+
);
|
|
403
|
+
const onMouseLeave = createComposedHandler(
|
|
404
|
+
handlers('onMouseLeave'),
|
|
405
|
+
handleLeave,
|
|
406
|
+
);
|
|
407
|
+
const onMouseOut = createComposedHandler(handlers('onMouseOut'), handleLeave);
|
|
408
|
+
const onTouchStart = createComposedHandler(
|
|
409
|
+
handlers('onTouchStart'),
|
|
410
|
+
handleTouchStart,
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
type ResolvedLinkStateProps = Omit<ComponentProps<'a'>, 'style'> & {
|
|
414
|
+
style?: JSX.CSSProperties;
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
// External links keep the caller's props as given, without state props.
|
|
418
|
+
const resolvedStateProps = Solid.createMemo(
|
|
419
|
+
(): ResolvedLinkStateProps => {
|
|
420
|
+
const stateProps: ResolvedLinkStateProps = externalLink()
|
|
421
|
+
? EMPTY_OBJECT
|
|
422
|
+
: ((isActive()
|
|
423
|
+
? functionalUpdate(activeProps() as any, {})
|
|
424
|
+
: functionalUpdate(inactiveProps(), {})) ?? EMPTY_OBJECT);
|
|
425
|
+
if (process.env.NODE_ENV !== 'production')
|
|
426
|
+
warnUnknownStateKeys(stateProps, extraStateKeys);
|
|
427
|
+
return stateProps;
|
|
428
|
+
},
|
|
429
|
+
{ lazy: true },
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
// State props override the base props, target included: a `_blank` from
|
|
433
|
+
// activeProps opens a new browsing context instead of navigating in place.
|
|
434
|
+
const resolvedTarget = () => {
|
|
435
|
+
const target = (resolvedStateProps() as { target?: string }).target;
|
|
436
|
+
return target !== undefined ? target : local.target;
|
|
437
|
+
};
|
|
438
|
+
const stateOrBase = (key: string): unknown => {
|
|
439
|
+
const state = (resolvedStateProps() as Record<string, unknown>)[key];
|
|
440
|
+
return state !== undefined
|
|
441
|
+
? state
|
|
442
|
+
: (propsSafeToSpread as Record<string, unknown>)[key];
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
const resolvedClass = Solid.createMemo(
|
|
446
|
+
() => {
|
|
447
|
+
if (simpleStyling()) return isActive() ? 'active' : undefined;
|
|
448
|
+
return (
|
|
449
|
+
[local.class, resolvedStateProps().class].filter(Boolean).join(' ') ||
|
|
450
|
+
undefined
|
|
451
|
+
);
|
|
452
|
+
},
|
|
453
|
+
{ lazy: true },
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
const resolvedStyle = Solid.createMemo(
|
|
457
|
+
() => {
|
|
458
|
+
if (simpleStyling()) return local.style;
|
|
459
|
+
const style = { ...local.style, ...resolvedStateProps().style };
|
|
460
|
+
return hasKeys(style) ? style : undefined;
|
|
461
|
+
},
|
|
462
|
+
{ lazy: true },
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
// The returned object must be a plain object with a stable key set so the
|
|
466
|
+
// consuming spread() never enumerates through proxy traps. Reactivity lives
|
|
467
|
+
// in the property getters; values that no longer apply resolve to undefined,
|
|
468
|
+
// which spread()/assign() treats as attribute removal. Keys returned by
|
|
469
|
+
// activeProps/inactiveProps are discovered once at setup: their values stay
|
|
470
|
+
// reactive, but a key first returned later is not applied, and development
|
|
471
|
+
// builds warn about it.
|
|
472
|
+
const extraStateKeys = new Set<string>();
|
|
473
|
+
Solid.untrack(() => {
|
|
474
|
+
for (const stateProps of [
|
|
475
|
+
functionalUpdate(activeProps() as any, {}),
|
|
476
|
+
functionalUpdate(inactiveProps(), {}),
|
|
477
|
+
]) {
|
|
478
|
+
if (stateProps) {
|
|
479
|
+
for (const key of Object.keys(stateProps)) {
|
|
480
|
+
if (key !== 'class' && key !== 'style') extraStateKeys.add(key);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
const composedRef = mergeRefs(setRef, (el: Element) => {
|
|
487
|
+
const r = _options().ref as any;
|
|
488
|
+
if (typeof r === 'function') r(el);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
const linkProps: Record<string, any> = {};
|
|
492
|
+
for (const key of Object.keys(propsSafeToSpread)) {
|
|
493
|
+
Object.defineProperty(
|
|
494
|
+
linkProps,
|
|
495
|
+
key,
|
|
496
|
+
Object.getOwnPropertyDescriptor(propsSafeToSpread, key)!,
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
for (const key of extraStateKeys) {
|
|
500
|
+
Object.defineProperty(linkProps, key, {
|
|
501
|
+
get: () => (resolvedStateProps() as Record<string, any>)[key],
|
|
502
|
+
enumerable: true,
|
|
503
|
+
configurable: true,
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const defineGetters = (getters: Record<string, () => any>) => {
|
|
508
|
+
for (const key of Object.keys(getters)) {
|
|
509
|
+
Object.defineProperty(linkProps, key, {
|
|
510
|
+
get: getters[key],
|
|
511
|
+
enumerable: true,
|
|
512
|
+
configurable: true,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
// The Link owns ref and onClick. Define them over any getter a state prop
|
|
518
|
+
// installed above; a plain assignment to a getter-only key throws.
|
|
519
|
+
for (const [key, value] of [
|
|
520
|
+
['ref', composedRef],
|
|
521
|
+
['onClick', onClick],
|
|
522
|
+
] as const)
|
|
523
|
+
Object.defineProperty(linkProps, key, {
|
|
524
|
+
value,
|
|
525
|
+
writable: true,
|
|
526
|
+
enumerable: true,
|
|
527
|
+
configurable: true,
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
// Intent preloading is the only thing these handlers do, and each of them
|
|
531
|
+
// already bails at a `preload() !== 'intent'` gate. Handing them out anyway
|
|
532
|
+
// is not free: Solid does not delegate mouseenter/mouseleave/focus/blur, so
|
|
533
|
+
// every anchor installs four real listeners that exist only to return. On a
|
|
534
|
+
// list view — a table of rows, a calendar of spans — that is four listeners
|
|
535
|
+
// per row for no behaviour at all.
|
|
536
|
+
//
|
|
537
|
+
// So when intent preloading is off, the property resolves to whatever the
|
|
538
|
+
// user passed (or undefined), and spread()/assign() installs nothing. The
|
|
539
|
+
// getters keep this reactive: flipping `preload` back to 'intent' re-runs
|
|
540
|
+
// the consuming spread, which attaches the composed handler then.
|
|
541
|
+
// Without intent preloading only the caller's and state handlers remain, so
|
|
542
|
+
// nothing is installed unless one of them exists.
|
|
543
|
+
const onIntent = (composed: (event: any) => void, key: string) => {
|
|
544
|
+
const passthrough = createComposedHandler(handlers(key as never));
|
|
545
|
+
return () =>
|
|
546
|
+
preload() === 'intent'
|
|
547
|
+
? composed
|
|
548
|
+
: (local as Record<string, unknown>)[key] || stateHandler(key)()
|
|
549
|
+
? passthrough
|
|
550
|
+
: undefined;
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
defineGetters({
|
|
554
|
+
onBlur: onIntent(onBlur, 'onBlur'),
|
|
555
|
+
onFocus: onIntent(onFocus, 'onFocus'),
|
|
556
|
+
onMouseEnter: onIntent(onMouseEnter, 'onMouseEnter'),
|
|
557
|
+
onMouseOver: onIntent(onMouseOver, 'onMouseOver'),
|
|
558
|
+
onMouseLeave: onIntent(onMouseLeave, 'onMouseLeave'),
|
|
559
|
+
onMouseOut: onIntent(onMouseOut, 'onMouseOut'),
|
|
560
|
+
onTouchStart: onIntent(onTouchStart, 'onTouchStart'),
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
defineGetters({
|
|
564
|
+
href: hrefOption,
|
|
565
|
+
disabled: () => !!local.disabled || linkDisabled(),
|
|
566
|
+
target: resolvedTarget,
|
|
567
|
+
// The Link owns these only while disabled or active, as upstream Links
|
|
568
|
+
// do; otherwise a value from the state props, then the base props, wins.
|
|
569
|
+
role: () => (linkDisabled() ? 'link' : stateOrBase('role')),
|
|
570
|
+
'aria-disabled': () =>
|
|
571
|
+
linkDisabled() ? 'true' : stateOrBase('aria-disabled'),
|
|
572
|
+
'data-status': () => (isActive() ? 'active' : stateOrBase('data-status')),
|
|
573
|
+
'aria-current': () => (isActive() ? 'page' : stateOrBase('aria-current')),
|
|
574
|
+
class: resolvedClass,
|
|
575
|
+
style: resolvedStyle,
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
return linkProps as any;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const STATIC_ACTIVE_PROPS = { class: 'active' };
|
|
582
|
+
const STATIC_ACTIVE_PROPS_GET = () => STATIC_ACTIVE_PROPS;
|
|
583
|
+
const EMPTY_OBJECT = {};
|
|
584
|
+
const STATIC_INACTIVE_PROPS_GET = () => EMPTY_OBJECT;
|
|
585
|
+
|
|
586
|
+
/** Call a JSX.EventHandlerUnion with the event. */
|
|
587
|
+
function callHandler<T, TEvent extends Event>(
|
|
588
|
+
event: TEvent & { currentTarget: T; target: Element },
|
|
589
|
+
handler: JSX.EventHandlerUnion<T, TEvent>,
|
|
590
|
+
) {
|
|
591
|
+
if (typeof handler === 'function') {
|
|
592
|
+
handler(event);
|
|
593
|
+
} else {
|
|
594
|
+
handler[0](handler[1], event);
|
|
595
|
+
}
|
|
596
|
+
return event.defaultPrevented;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/** Run each handler in order; one that prevents default skips the rest. */
|
|
600
|
+
function createComposedHandler<T, TEvent extends Event>(
|
|
601
|
+
getHandlers: readonly (() => JSX.EventHandlerUnion<T, TEvent> | undefined)[],
|
|
602
|
+
fallback?: (event: TEvent) => void,
|
|
603
|
+
) {
|
|
604
|
+
return (event: TEvent & { currentTarget: T; target: Element }) => {
|
|
605
|
+
for (const getHandler of getHandlers) {
|
|
606
|
+
const handler = getHandler();
|
|
607
|
+
if (handler && callHandler(event, handler)) return;
|
|
608
|
+
}
|
|
609
|
+
fallback?.(event);
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
export type UseLinkPropsOptions<
|
|
614
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
615
|
+
TFrom extends RoutePaths<TRouter['routeTree']> | string = string,
|
|
616
|
+
TTo extends string | undefined = '.',
|
|
617
|
+
TMaskFrom extends RoutePaths<TRouter['routeTree']> | string = TFrom,
|
|
618
|
+
TMaskTo extends string = '.',
|
|
619
|
+
> = ActiveLinkOptions<'a', TRouter, TFrom, TTo, TMaskFrom, TMaskTo> &
|
|
620
|
+
Omit<ComponentProps<'a'>, 'style'> & { style?: JSX.CSSProperties };
|
|
621
|
+
|
|
622
|
+
export type ActiveLinkOptions<
|
|
623
|
+
TComp = 'a',
|
|
624
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
625
|
+
TFrom extends string = string,
|
|
626
|
+
TTo extends string | undefined = '.',
|
|
627
|
+
TMaskFrom extends string = TFrom,
|
|
628
|
+
TMaskTo extends string = '.',
|
|
629
|
+
> = LinkOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo> &
|
|
630
|
+
ActiveLinkOptionProps<TComp>;
|
|
631
|
+
|
|
632
|
+
type ActiveLinkProps<TComp> = Partial<
|
|
633
|
+
LinkComponentSolidProps<TComp> & {
|
|
634
|
+
[key: `data-${string}`]: unknown;
|
|
635
|
+
}
|
|
636
|
+
>;
|
|
637
|
+
|
|
638
|
+
export interface ActiveLinkOptionProps<TComp = 'a'> {
|
|
639
|
+
/**
|
|
640
|
+
* A function that returns additional props for the `active` state of this link.
|
|
641
|
+
* These props override other props passed to the link (`style`'s are merged, `class`'s are concatenated)
|
|
642
|
+
*/
|
|
643
|
+
activeProps?: ActiveLinkProps<TComp> | (() => ActiveLinkProps<TComp>);
|
|
644
|
+
/**
|
|
645
|
+
* A function that returns additional props for the `inactive` state of this link.
|
|
646
|
+
* These props override other props passed to the link (`style`'s are merged, `class`'s are concatenated)
|
|
647
|
+
*/
|
|
648
|
+
inactiveProps?: ActiveLinkProps<TComp> | (() => ActiveLinkProps<TComp>);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
export type LinkProps<
|
|
652
|
+
TComp = 'a',
|
|
653
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
654
|
+
TFrom extends string = string,
|
|
655
|
+
TTo extends string | undefined = '.',
|
|
656
|
+
TMaskFrom extends string = TFrom,
|
|
657
|
+
TMaskTo extends string = '.',
|
|
658
|
+
> = ActiveLinkOptions<TComp, TRouter, TFrom, TTo, TMaskFrom, TMaskTo> &
|
|
659
|
+
LinkPropsChildren;
|
|
660
|
+
|
|
661
|
+
export interface LinkPropsChildren {
|
|
662
|
+
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
|
|
663
|
+
children?: JSX.Element | ((state: { isActive: boolean }) => JSX.Element);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
type LinkComponentSolidProps<TComp> = TComp extends ValidComponent
|
|
667
|
+
? Omit<ComponentProps<TComp>, keyof CreateLinkProps>
|
|
668
|
+
: never;
|
|
669
|
+
|
|
670
|
+
export type LinkComponentProps<
|
|
671
|
+
TComp = 'a',
|
|
672
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
673
|
+
TFrom extends string = string,
|
|
674
|
+
TTo extends string | undefined = '.',
|
|
675
|
+
TMaskFrom extends string = TFrom,
|
|
676
|
+
TMaskTo extends string = '.',
|
|
677
|
+
> = LinkComponentSolidProps<TComp> &
|
|
678
|
+
LinkProps<TComp, TRouter, TFrom, TTo, TMaskFrom, TMaskTo>;
|
|
679
|
+
|
|
680
|
+
export type CreateLinkProps = LinkProps<
|
|
681
|
+
any,
|
|
682
|
+
any,
|
|
683
|
+
string,
|
|
684
|
+
string,
|
|
685
|
+
string,
|
|
686
|
+
string
|
|
687
|
+
>;
|
|
688
|
+
|
|
689
|
+
export type LinkComponent<
|
|
690
|
+
in out TComp,
|
|
691
|
+
in out TDefaultFrom extends string = string,
|
|
692
|
+
> = <
|
|
693
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
694
|
+
const TFrom extends string = TDefaultFrom,
|
|
695
|
+
const TTo extends string | undefined = undefined,
|
|
696
|
+
const TMaskFrom extends string = TFrom,
|
|
697
|
+
const TMaskTo extends string = '',
|
|
698
|
+
>(
|
|
699
|
+
props: LinkComponentProps<TComp, TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
|
|
700
|
+
) => JSX.Element;
|
|
701
|
+
|
|
702
|
+
export interface LinkComponentRoute<
|
|
703
|
+
in out TDefaultFrom extends string = string,
|
|
704
|
+
> {
|
|
705
|
+
defaultFrom: TDefaultFrom;
|
|
706
|
+
<
|
|
707
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
708
|
+
const TTo extends string | undefined = undefined,
|
|
709
|
+
const TMaskTo extends string = '',
|
|
710
|
+
>(
|
|
711
|
+
props: LinkComponentProps<
|
|
712
|
+
'a',
|
|
713
|
+
TRouter,
|
|
714
|
+
this['defaultFrom'],
|
|
715
|
+
TTo,
|
|
716
|
+
this['defaultFrom'],
|
|
717
|
+
TMaskTo
|
|
718
|
+
>,
|
|
719
|
+
): JSX.Element;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
export function createLink<const TComp>(
|
|
723
|
+
Comp: Constrain<TComp, any, (props: CreateLinkProps) => JSX.Element>,
|
|
724
|
+
): LinkComponent<TComp> {
|
|
725
|
+
return props => <Link {...props} _asChild={Comp} />;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
export const Link: LinkComponent<'a'> = props => {
|
|
729
|
+
const [local, rest] = splitProps(props as typeof props & { _asChild: any }, [
|
|
730
|
+
'_asChild',
|
|
731
|
+
'children',
|
|
732
|
+
]);
|
|
733
|
+
const [_, linkProps] = splitProps(useLinkProps(rest as unknown as any), [
|
|
734
|
+
'type',
|
|
735
|
+
]);
|
|
736
|
+
|
|
737
|
+
// Resolve children once using Solid.children to avoid
|
|
738
|
+
// re-accessing the children getter (which in Solid 2.0 would
|
|
739
|
+
// re-invoke createComponent each time for JSX children).
|
|
740
|
+
const resolvedChildren = Solid.children(() => local.children as JSX.Element);
|
|
741
|
+
const children = () => {
|
|
742
|
+
const ch = resolvedChildren();
|
|
743
|
+
if (typeof ch === 'function') {
|
|
744
|
+
return (ch as Function)({
|
|
745
|
+
get isActive() {
|
|
746
|
+
return (linkProps as any)['data-status'] === 'active';
|
|
747
|
+
},
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
return ch;
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
if (local._asChild === 'svg') {
|
|
755
|
+
const [_, svgLinkProps] = splitProps(linkProps as any, ['class']);
|
|
756
|
+
return (
|
|
757
|
+
<svg>
|
|
758
|
+
<a {...svgLinkProps}>{children()}</a>
|
|
759
|
+
</svg>
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
if (!local._asChild) {
|
|
764
|
+
return <a {...linkProps}>{children()}</a>;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
return (
|
|
768
|
+
<Dynamic component={local._asChild as ValidComponent} {...linkProps}>
|
|
769
|
+
{children()}
|
|
770
|
+
</Dynamic>
|
|
771
|
+
);
|
|
772
|
+
};
|
|
773
|
+
|
|
774
|
+
function isCtrlEvent(e: MouseEvent) {
|
|
775
|
+
return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const warnedStateKeys = new Set<string>();
|
|
779
|
+
|
|
780
|
+
function warnUnknownStateKeys(
|
|
781
|
+
stateProps: object,
|
|
782
|
+
knownKeys: ReadonlySet<string>,
|
|
783
|
+
) {
|
|
784
|
+
for (const key of Object.keys(stateProps)) {
|
|
785
|
+
if (
|
|
786
|
+
key === 'class' ||
|
|
787
|
+
key === 'style' ||
|
|
788
|
+
knownKeys.has(key) ||
|
|
789
|
+
warnedStateKeys.has(key)
|
|
790
|
+
)
|
|
791
|
+
continue;
|
|
792
|
+
warnedStateKeys.add(key);
|
|
793
|
+
console.warn(
|
|
794
|
+
`Link activeProps/inactiveProps returned "${key}" after the Link was created; return every key from the start (use undefined for "unset") so it can be applied.`,
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function warnBlockedLink(href: string) {
|
|
800
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
801
|
+
console.warn(`Blocked Link with dangerous protocol: ${href}`);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Classifies a `to` by URL scheme, as upstream router Links do: the URL when
|
|
807
|
+
* the router's protocol allowlist admits it, `null` when it is blocked, and
|
|
808
|
+
* `undefined` for a route path that still needs route resolution.
|
|
809
|
+
*/
|
|
810
|
+
function resolveExternalLink(
|
|
811
|
+
to: unknown,
|
|
812
|
+
protocolAllowlist: AnyRouter['protocolAllowlist'],
|
|
813
|
+
): string | null | undefined {
|
|
814
|
+
const scheme = typeof to === 'string' ? getUrlScheme(to) : undefined;
|
|
815
|
+
if (!scheme) return undefined;
|
|
816
|
+
if (!protocolAllowlist.has(scheme)) {
|
|
817
|
+
warnBlockedLink(to as string);
|
|
818
|
+
return null;
|
|
819
|
+
}
|
|
820
|
+
return to as string;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* The display href of a built location. publicHref is the full URL when a
|
|
825
|
+
* rewrite changed the origin and the origin-stripped path otherwise, so no URL
|
|
826
|
+
* is constructed in the hot path. A rewritten or history-formatted href with a
|
|
827
|
+
* disallowed protocol resolves to `undefined` and the link renders blocked.
|
|
828
|
+
*/
|
|
829
|
+
function getHrefOption(
|
|
830
|
+
next: ParsedLocation,
|
|
831
|
+
router: AnyRouter,
|
|
832
|
+
): string | undefined {
|
|
833
|
+
const location = next.maskedLocation ?? next;
|
|
834
|
+
const href = location.external
|
|
835
|
+
? location.publicHref
|
|
836
|
+
: router.history.createHref(location.publicHref) || '/';
|
|
837
|
+
if (
|
|
838
|
+
(location.external || href !== location.publicHref) &&
|
|
839
|
+
isDangerousProtocol(href, router.protocolAllowlist)
|
|
840
|
+
) {
|
|
841
|
+
warnBlockedLink(href);
|
|
842
|
+
return undefined;
|
|
843
|
+
}
|
|
844
|
+
return href;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
export type LinkOptionsFnOptions<
|
|
848
|
+
TOptions,
|
|
849
|
+
TComp,
|
|
850
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
851
|
+
> =
|
|
852
|
+
TOptions extends ReadonlyArray<any>
|
|
853
|
+
? ValidateLinkOptionsArray<TRouter, TOptions, string, TComp>
|
|
854
|
+
: ValidateLinkOptions<TRouter, TOptions, string, TComp>;
|
|
855
|
+
|
|
856
|
+
export type LinkOptionsFn<TComp> = <
|
|
857
|
+
const TOptions,
|
|
858
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
859
|
+
>(
|
|
860
|
+
options: LinkOptionsFnOptions<TOptions, TComp, TRouter>,
|
|
861
|
+
) => TOptions;
|
|
862
|
+
|
|
863
|
+
export const linkOptions: LinkOptionsFn<'a'> = options => {
|
|
864
|
+
return options as any;
|
|
865
|
+
};
|