@wular/pnext 0.0.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/README.md +153 -0
- package/bin/pnext +67 -0
- package/config/lint/base.js +48 -0
- package/config/ts/base.json +26 -0
- package/config/ts/react.json +13 -0
- package/package.json +70 -0
- package/reference/compat.md +63 -0
- package/reference/config.md +120 -0
- package/reference/css.md +58 -0
- package/reference/dev.md +69 -0
- package/reference/env.md +40 -0
- package/reference/metadata.md +86 -0
- package/reference/navigation.md +149 -0
- package/reference/overview.md +35 -0
- package/reference/performance.md +97 -0
- package/reference/rendering.md +127 -0
- package/reference/routing.md +167 -0
- package/reference/typegen.md +64 -0
- package/src/api/cache.ts +80 -0
- package/src/api/client-cache.ts +9 -0
- package/src/api/client-navigation.ts +279 -0
- package/src/api/dynamic.tsx +102 -0
- package/src/api/link.tsx +119 -0
- package/src/api/navigation.ts +198 -0
- package/src/api/router/events.ts +53 -0
- package/src/api/router/history.ts +70 -0
- package/src/api/router/hub.ts +194 -0
- package/src/api/router/policies.ts +107 -0
- package/src/api/router/runtime.ts +5238 -0
- package/src/api/router/types.ts +299 -0
- package/src/api/router.ts +167 -0
- package/src/api/server.ts +323 -0
- package/src/api/suspense.ts +16 -0
- package/src/cache/context.ts +61 -0
- package/src/cli/adapters/vercel-warm.ts +375 -0
- package/src/cli/adapters/vercel.ts +1310 -0
- package/src/cli/analyze-print.ts +181 -0
- package/src/cli/analyze.ts +328 -0
- package/src/cli/boot-trace.ts +29 -0
- package/src/cli/build.ts +3114 -0
- package/src/cli/dev.ts +276 -0
- package/src/cli/index.ts +196 -0
- package/src/cli/named-bin.ts +119 -0
- package/src/cli/serve-ui.ts +160 -0
- package/src/cli/start.ts +1425 -0
- package/src/client/build.ts +2136 -0
- package/src/client/chunk-fold.ts +526 -0
- package/src/client/entry.ts +1525 -0
- package/src/client/paths.ts +22 -0
- package/src/client/prebuilt.ts +621 -0
- package/src/client/profile.ts +75 -0
- package/src/client/react-compiler.ts +94 -0
- package/src/client/reference-stub.ts +145 -0
- package/src/client/reference.ts +47 -0
- package/src/compat/actions/action-client.ts +531 -0
- package/src/compat/actions/action-dispatch.ts +676 -0
- package/src/compat/actions/action-router.ts +40 -0
- package/src/compat/actions/action-shared.ts +95 -0
- package/src/compat/actions/client-plugin.ts +135 -0
- package/src/compat/actions/client-stub.ts +65 -0
- package/src/compat/actions/config.ts +164 -0
- package/src/compat/actions/detect.ts +208 -0
- package/src/compat/actions/discovery.ts +244 -0
- package/src/compat/actions/early-submit.ts +40 -0
- package/src/compat/actions/endpoint.ts +602 -0
- package/src/compat/actions/flight.ts +52 -0
- package/src/compat/actions/form-state.ts +73 -0
- package/src/compat/actions/hoist.ts +485 -0
- package/src/compat/actions/ids.ts +39 -0
- package/src/compat/actions/index.ts +41 -0
- package/src/compat/actions/instances.ts +144 -0
- package/src/compat/actions/origin.ts +109 -0
- package/src/compat/actions/protocol.ts +125 -0
- package/src/compat/actions/registry.ts +74 -0
- package/src/compat/actions/rewrite.ts +282 -0
- package/src/compat/actions/serve.ts +414 -0
- package/src/compat/actions/server-tag.ts +21 -0
- package/src/compat/actions/unrecognized-error.ts +30 -0
- package/src/compat/adapter/build-complete.ts +257 -0
- package/src/compat/bundler/bun-externals.ts +53 -0
- package/src/compat/bundler/cjs-exports.ts +542 -0
- package/src/compat/bundler/config.ts +363 -0
- package/src/compat/bundler/externals.ts +34 -0
- package/src/compat/bundler/import-meta-url.ts +60 -0
- package/src/compat/bundler/modularize-imports.ts +119 -0
- package/src/compat/bundler/new-url-asset.ts +87 -0
- package/src/compat/bundler/optimize-package-imports.ts +273 -0
- package/src/compat/bundler/polyfill.ts +88 -0
- package/src/compat/bundler/react-compiler.ts +61 -0
- package/src/compat/bundler/react-profiler.tsx +25 -0
- package/src/compat/bundler/relay-transform.ts +116 -0
- package/src/compat/bundler/require-context.ts +281 -0
- package/src/compat/bundler/resolve-extensions.ts +75 -0
- package/src/compat/bundler/source-cache.ts +61 -0
- package/src/compat/bundler/static-imports.ts +25 -0
- package/src/compat/bundler/symlink-imports.ts +119 -0
- package/src/compat/bundler/tsconfig-paths.ts +50 -0
- package/src/compat/bundler/wasm.ts +153 -0
- package/src/compat/bundler/webpack-loaders.ts +685 -0
- package/src/compat/bundler/worker.ts +278 -0
- package/src/compat/cache/build-flags.ts +81 -0
- package/src/compat/cache/build-prerender-errors.ts +163 -0
- package/src/compat/cache/custom-handler.ts +159 -0
- package/src/compat/cache/fetch-patch.ts +745 -0
- package/src/compat/cache/handler.ts +100 -0
- package/src/compat/cache/modern-handler.ts +275 -0
- package/src/compat/cache/resume-data-cache.ts +143 -0
- package/src/compat/cache/revalidate.ts +759 -0
- package/src/compat/cache/runtime-error.ts +124 -0
- package/src/compat/cache/use-cache-transform.ts +961 -0
- package/src/compat/cache/use-cache.ts +1695 -0
- package/src/compat/cache-control.ts +269 -0
- package/src/compat/client/base-path.ts +64 -0
- package/src/compat/client/css-order.ts +36 -0
- package/src/compat/client/errors/control-flow.ts +92 -0
- package/src/compat/client/errors/error-boundary.ts +222 -0
- package/src/compat/client/errors/global-error.ts +238 -0
- package/src/compat/client/errors/install.ts +217 -0
- package/src/compat/client/errors/lazy.ts +53 -0
- package/src/compat/client/errors/primitive-throw.ts +126 -0
- package/src/compat/client/errors/soft-refresh.ts +14 -0
- package/src/compat/client/link-status.ts +86 -0
- package/src/compat/client/nav-compat-runtime.ts +57 -0
- package/src/compat/client/nav-compat.ts +42 -0
- package/src/compat/client/navigation-scroll.ts +154 -0
- package/src/compat/client/optimistic-routing.ts +206 -0
- package/src/compat/client/prefetch-cache.ts +111 -0
- package/src/compat/client/route-announcer.ts +72 -0
- package/src/compat/client/segment-cache-policy.ts +159 -0
- package/src/compat/client/segment-cache.ts +1077 -0
- package/src/compat/client/segment-prefetch.ts +375 -0
- package/src/compat/client/trailing-slash.ts +24 -0
- package/src/compat/css/chunking.ts +254 -0
- package/src/compat/css/inline-css.ts +73 -0
- package/src/compat/css/lightningcss.ts +90 -0
- package/src/compat/css/modules.ts +373 -0
- package/src/compat/css/nonce.ts +30 -0
- package/src/compat/css/sass-plugin.ts +65 -0
- package/src/compat/css/sass.ts +392 -0
- package/src/compat/css/styled-jsx-runtime.ts +80 -0
- package/src/compat/css/styled-jsx.ts +49 -0
- package/src/compat/edge-runtime.ts +71 -0
- package/src/compat/export/client.ts +112 -0
- package/src/compat/export/index.ts +272 -0
- package/src/compat/export/standalone.ts +207 -0
- package/src/compat/image-optimizer/cache.ts +119 -0
- package/src/compat/image-optimizer/detect.ts +143 -0
- package/src/compat/image-optimizer/index.ts +601 -0
- package/src/compat/image-optimizer/source.ts +243 -0
- package/src/compat/index.ts +458 -0
- package/src/compat/lifecycle/after-scope.ts +86 -0
- package/src/compat/lifecycle/after.ts +173 -0
- package/src/compat/lifecycle/error-funnel.ts +306 -0
- package/src/compat/lifecycle/error-serialize.ts +87 -0
- package/src/compat/lifecycle/error-ui.ts +167 -0
- package/src/compat/lifecycle/instrumentation-client.ts +138 -0
- package/src/compat/lifecycle/instrumentation.ts +277 -0
- package/src/compat/lifecycle/node-console.ts +19 -0
- package/src/compat/lifecycle/testmode.ts +263 -0
- package/src/compat/mdx/compile.ts +219 -0
- package/src/compat/mdx/next-mdx-stub.ts +46 -0
- package/src/compat/mdx/plugin.ts +37 -0
- package/src/compat/metadata-route-artifacts.ts +458 -0
- package/src/compat/metadata.ts +295 -0
- package/src/compat/middleware/manifest.ts +210 -0
- package/src/compat/misc/action-return.ts +173 -0
- package/src/compat/next/cache.ts +211 -0
- package/src/compat/next/canonical-url.ts +35 -0
- package/src/compat/next/client-cache.ts +57 -0
- package/src/compat/next/client-navigation.ts +313 -0
- package/src/compat/next/client-only.ts +3 -0
- package/src/compat/next/client-script.tsx +215 -0
- package/src/compat/next/client-server.ts +39 -0
- package/src/compat/next/config-loader.ts +569 -0
- package/src/compat/next/config.ts +29 -0
- package/src/compat/next/constants.cjs +6 -0
- package/src/compat/next/constants.ts +6 -0
- package/src/compat/next/custom-server.ts +236 -0
- package/src/compat/next/dist/client/components/app-router-headers.ts +32 -0
- package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +38 -0
- package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -0
- package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -0
- package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -0
- package/src/compat/next/dynamic.tsx +46 -0
- package/src/compat/next/error.tsx +148 -0
- package/src/compat/next/font/cache.ts +171 -0
- package/src/compat/next/font/google.ts +2 -0
- package/src/compat/next/font/index.ts +8 -0
- package/src/compat/next/font/local.ts +5 -0
- package/src/compat/next/font/runtime-client.ts +71 -0
- package/src/compat/next/font/runtime.ts +974 -0
- package/src/compat/next/font/shared.ts +281 -0
- package/src/compat/next/form.tsx +156 -0
- package/src/compat/next/head.tsx +10 -0
- package/src/compat/next/headers.ts +247 -0
- package/src/compat/next/image/config.ts +196 -0
- package/src/compat/next/image/optimizer.ts +96 -0
- package/src/compat/next/image/patterns.ts +103 -0
- package/src/compat/next/image/shared.ts +141 -0
- package/src/compat/next/image/static-metadata.ts +283 -0
- package/src/compat/next/image/validate.ts +269 -0
- package/src/compat/next/image-client.tsx +215 -0
- package/src/compat/next/image-props.ts +575 -0
- package/src/compat/next/image-usage.ts +102 -0
- package/src/compat/next/image.tsx +56 -0
- package/src/compat/next/index.ts +1 -0
- package/src/compat/next/legacy-image.tsx +97 -0
- package/src/compat/next/link-usage.ts +29 -0
- package/src/compat/next/link-validation-transform.ts +200 -0
- package/src/compat/next/link.tsx +466 -0
- package/src/compat/next/navigation.cjs +21 -0
- package/src/compat/next/navigation.ts +188 -0
- package/src/compat/next/offline.ts +51 -0
- package/src/compat/next/og.ts +324 -0
- package/src/compat/next/optimistic-route-state.ts +188 -0
- package/src/compat/next/preferred-region.ts +39 -0
- package/src/compat/next/redirects.ts +131 -0
- package/src/compat/next/resource-hints.ts +136 -0
- package/src/compat/next/rewrites.ts +350 -0
- package/src/compat/next/root-params.ts +142 -0
- package/src/compat/next/router.cjs +49 -0
- package/src/compat/next/router.ts +143 -0
- package/src/compat/next/script.tsx +355 -0
- package/src/compat/next/server-only.ts +3 -0
- package/src/compat/next/server.ts +28 -0
- package/src/compat/next/svgr.ts +58 -0
- package/src/compat/next/telemetry.ts +77 -0
- package/src/compat/next/user-agent.ts +100 -0
- package/src/compat/next/web-vitals.ts +56 -0
- package/src/compat/otel/api.ts +95 -0
- package/src/compat/otel/client-trace-metadata.ts +71 -0
- package/src/compat/otel/fetch-span.ts +77 -0
- package/src/compat/otel/tracer.ts +944 -0
- package/src/compat/pages/client-plugin.ts +108 -0
- package/src/compat/pages/index.ts +527 -0
- package/src/compat/pages/router-state.ts +94 -0
- package/src/compat/ppr/io.ts +38 -0
- package/src/compat/ppr/missing-root-params.ts +105 -0
- package/src/compat/ppr/root-params-scan.ts +164 -0
- package/src/compat/ppr/root-params-transform.ts +75 -0
- package/src/compat/ppr/root-params.ts +129 -0
- package/src/compat/ppr/segment-config-incompat.ts +34 -0
- package/src/compat/protocol.ts +202 -0
- package/src/compat/react/client.ts +59 -0
- package/src/compat/react/compiler-runtime.ts +60 -0
- package/src/compat/react/dom-client.ts +115 -0
- package/src/compat/react/dom-react-server.ts +20 -0
- package/src/compat/react/dom-server.ts +40 -0
- package/src/compat/react/dom.ts +154 -0
- package/src/compat/react/preact.ts +522 -0
- package/src/compat/react/react-server.ts +84 -0
- package/src/compat/react/router-shim.ts +26 -0
- package/src/compat/react/server-component-use.ts +48 -0
- package/src/compat/react/server-inserted-html.ts +87 -0
- package/src/compat/react/server.ts +156 -0
- package/src/compat/react/view-transition.ts +60 -0
- package/src/compat/register/actions.ts +875 -0
- package/src/compat/register/boot.ts +141 -0
- package/src/compat/register/build-tier.ts +11 -0
- package/src/compat/register/build.ts +182 -0
- package/src/compat/register/bundler.ts +587 -0
- package/src/compat/register/cache.ts +85 -0
- package/src/compat/register/client-errors.ts +18 -0
- package/src/compat/register/config.ts +17 -0
- package/src/compat/register/css-extras.ts +120 -0
- package/src/compat/register/edge-runtime.ts +6 -0
- package/src/compat/register/errors.ts +46 -0
- package/src/compat/register/export.ts +23 -0
- package/src/compat/register/font.ts +36 -0
- package/src/compat/register/hooks.ts +34 -0
- package/src/compat/register/image.ts +133 -0
- package/src/compat/register/index.ts +111 -0
- package/src/compat/register/instrumentation-client.ts +35 -0
- package/src/compat/register/lifecycle.ts +86 -0
- package/src/compat/register/mdx.ts +48 -0
- package/src/compat/register/middleware.ts +36 -0
- package/src/compat/register/misc.ts +44 -0
- package/src/compat/register/otel.ts +288 -0
- package/src/compat/register/pages-api.ts +473 -0
- package/src/compat/register/ppr.ts +56 -0
- package/src/compat/register/protocol.ts +57 -0
- package/src/compat/register/proxy.ts +127 -0
- package/src/compat/register/render.ts +268 -0
- package/src/compat/register/routing.ts +410 -0
- package/src/compat/register/segment.ts +1903 -0
- package/src/compat/register/static-image.ts +21 -0
- package/src/compat/register/typed-routes.ts +35 -0
- package/src/compat/register/usecache.ts +131 -0
- package/src/compat/register/validation.ts +56 -0
- package/src/compat/segment/loading-boundary.ts +113 -0
- package/src/compat/segment/page-slot.ts +200 -0
- package/src/compat/segment/tree.ts +481 -0
- package/src/compat/segment/vary-key.ts +102 -0
- package/src/compat/segment/vary-params.ts +551 -0
- package/src/compat/static-params.ts +33 -0
- package/src/compat/tsconfig-defaults.ts +301 -0
- package/src/compat/typecheck/index.ts +1481 -0
- package/src/compat/typecheck/worker.ts +26 -0
- package/src/compat/typed-routes/index.ts +92 -0
- package/src/compat/typed-routes/manifest.ts +356 -0
- package/src/compat/typed-routes/typegen.ts +566 -0
- package/src/compat/validation/errors.ts +159 -0
- package/src/compat/validation/index.ts +1770 -0
- package/src/compat/validation/prerender-diagnostics.ts +1508 -0
- package/src/compat-bootstrap.ts +67 -0
- package/src/config.ts +218 -0
- package/src/css/build.ts +697 -0
- package/src/css/index.ts +2 -0
- package/src/css/postcss.ts +236 -0
- package/src/css/worker.ts +34 -0
- package/src/dev/client-actions.ts +35 -0
- package/src/dev/client-chunk-store.ts +92 -0
- package/src/dev/client-key-cache.ts +178 -0
- package/src/dev/global-css-cache.ts +212 -0
- package/src/dev/imports.ts +2430 -0
- package/src/dev/module-cache.ts +721 -0
- package/src/dev/module-generations.ts +38 -0
- package/src/dev/module-transform.ts +188 -0
- package/src/dev/node-module-bundle-cache.ts +63 -0
- package/src/dev/restart-cache.ts +10 -0
- package/src/dev/route-bundle-key-cache.ts +154 -0
- package/src/dev/route-facts-cache.ts +223 -0
- package/src/dev/server.ts +1710 -0
- package/src/dynamic/source.ts +307 -0
- package/src/dynamic/tree-shake.ts +262 -0
- package/src/env.ts +92 -0
- package/src/extensions.ts +1898 -0
- package/src/index.ts +34 -0
- package/src/internal.ts +43 -0
- package/src/islands/boundary-error.ts +8 -0
- package/src/islands/static-children.ts +37 -0
- package/src/islands/static-slots.ts +106 -0
- package/src/ppr-postpone.ts +24 -0
- package/src/ppr.ts +784 -0
- package/src/proxy.ts +752 -0
- package/src/render/hooks.ts +384 -0
- package/src/render/index.ts +1 -0
- package/src/render/island-context.ts +47 -0
- package/src/render/metadata.ts +857 -0
- package/src/render/renderer.ts +7391 -0
- package/src/render/resource-hints.ts +44 -0
- package/src/render/slots.tsx +679 -0
- package/src/request/context.ts +396 -0
- package/src/resolve/engine.ts +219 -0
- package/src/resolve/imports.ts +1104 -0
- package/src/resolve/scan-facts.ts +474 -0
- package/src/resolve/source-text.ts +86 -0
- package/src/routing/forwarded.ts +41 -0
- package/src/routing/handler.ts +271 -0
- package/src/routing/href.ts +203 -0
- package/src/routing/metadata.ts +1018 -0
- package/src/routing/request-runtime.ts +43 -0
- package/src/routing/routes.ts +2560 -0
- package/src/routing/slots.ts +432 -0
- package/src/runtime/server.ts +3453 -0
- package/src/runtime/vendor.ts +1160 -0
- package/src/style-modules.d.ts +9 -0
- package/src/typegen.ts +151 -0
- package/src/types.ts +725 -0
- package/src/utils/ansi.ts +9 -0
- package/src/utils/content-type.ts +31 -0
- package/src/utils/decode.ts +7 -0
- package/src/utils/dev-profile.ts +31 -0
- package/src/utils/error-log.ts +29 -0
- package/src/utils/fs-cache.ts +31 -0
- package/src/utils/fs.ts +119 -0
- package/src/utils/html.ts +46 -0
- package/src/utils/serialize.ts +378 -0
- package/src/utils/source.ts +35 -0
- package/src/utils/verbose.ts +39 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
cloneElement,
|
|
4
|
+
createElement,
|
|
5
|
+
forwardRef,
|
|
6
|
+
useContext,
|
|
7
|
+
useEffect,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
type MutableRefObject,
|
|
11
|
+
type ReactElement,
|
|
12
|
+
type ReactNode,
|
|
13
|
+
type Ref,
|
|
14
|
+
} from 'preact/compat';
|
|
15
|
+
import type { JSX } from 'preact';
|
|
16
|
+
import { usePrefetchLifecycle } from '../../api/link';
|
|
17
|
+
import { blockJavascriptUrl, isJavascriptUrl } from '../../api/router';
|
|
18
|
+
import { applyTrailingSlash, routeHref, type SearchInput } from '../../routing/href';
|
|
19
|
+
import { skipTrailingSlashRedirect } from '../client/trailing-slash';
|
|
20
|
+
import type { PrefetchMode } from '../../types';
|
|
21
|
+
import {
|
|
22
|
+
LinkStatusContext,
|
|
23
|
+
linkPending,
|
|
24
|
+
navigateWithStatus,
|
|
25
|
+
subscribeLinkStatus,
|
|
26
|
+
} from '../client/link-status';
|
|
27
|
+
import { addBasePath } from '../client/base-path';
|
|
28
|
+
import { addTransitionType } from '../react/view-transition';
|
|
29
|
+
|
|
30
|
+
const reactClientReferenceSymbol = Symbol.for('react.client.reference');
|
|
31
|
+
|
|
32
|
+
// Client-graph bundler define (compat/register/bundler.ts): false drops the
|
|
33
|
+
// whole legacy child path from a bundle whose app provably never renders
|
|
34
|
+
// `<Link legacyBehavior>`. Undefined (server render, unbundled consumer) keeps
|
|
35
|
+
// it, so the gate can only ever remove bytes an app proved it cannot use.
|
|
36
|
+
declare const __PNEXT_LINK_LEGACY__: boolean;
|
|
37
|
+
type LegacyChildKind =
|
|
38
|
+
| 'server-sync'
|
|
39
|
+
| 'server-async'
|
|
40
|
+
| 'server-async-through-client'
|
|
41
|
+
| 'lazy'
|
|
42
|
+
| 'client-lazy';
|
|
43
|
+
|
|
44
|
+
export interface UrlObject {
|
|
45
|
+
pathname?: string;
|
|
46
|
+
query?: SearchInput;
|
|
47
|
+
hash?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface NavigateEvent {
|
|
51
|
+
preventDefault(): void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type LinkProps = Omit<JSX.HTMLAttributes<HTMLAnchorElement>, 'href' | 'onClick' | 'ref'> & {
|
|
55
|
+
href: string | URL | UrlObject;
|
|
56
|
+
as?: string | URL | UrlObject;
|
|
57
|
+
replace?: boolean;
|
|
58
|
+
scroll?: boolean;
|
|
59
|
+
prefetch?: boolean | 'auto' | 'unstable_forceStale' | null;
|
|
60
|
+
/** Hovering the link upgrades its prefetch to include the dynamic data. */
|
|
61
|
+
unstable_dynamicOnHover?: boolean;
|
|
62
|
+
locale?: string | false;
|
|
63
|
+
legacyBehavior?: boolean;
|
|
64
|
+
passHref?: boolean;
|
|
65
|
+
onClick?: JSX.MouseEventHandler<HTMLAnchorElement>;
|
|
66
|
+
onNavigate?: (event: NavigateEvent) => void;
|
|
67
|
+
/**
|
|
68
|
+
* View-transition types declared for the navigation this Link starts, so
|
|
69
|
+
* `:active-view-transition-type(...)` rules match while it runs. Consumed
|
|
70
|
+
* here (never forwarded to the anchor as an attribute).
|
|
71
|
+
*/
|
|
72
|
+
transitionTypes?: string[];
|
|
73
|
+
onPrefetchStart?: () => void;
|
|
74
|
+
onPrefetchFinish?: () => void;
|
|
75
|
+
/** @internal Preserves legacy child classification across server resolution. */
|
|
76
|
+
__pnextLegacyChildKind?: LegacyChildKind;
|
|
77
|
+
ref?: Ref<HTMLAnchorElement>;
|
|
78
|
+
children?: ReactNode;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const Link = forwardRef<HTMLAnchorElement, LinkProps>(function Link(
|
|
82
|
+
{
|
|
83
|
+
href,
|
|
84
|
+
as,
|
|
85
|
+
prefetch = null,
|
|
86
|
+
unstable_dynamicOnHover,
|
|
87
|
+
replace,
|
|
88
|
+
scroll,
|
|
89
|
+
locale: _locale,
|
|
90
|
+
legacyBehavior,
|
|
91
|
+
passHref,
|
|
92
|
+
onClick,
|
|
93
|
+
onNavigate,
|
|
94
|
+
transitionTypes,
|
|
95
|
+
onPrefetchStart,
|
|
96
|
+
onPrefetchFinish,
|
|
97
|
+
__pnextLegacyChildKind,
|
|
98
|
+
children,
|
|
99
|
+
...anchorProps
|
|
100
|
+
},
|
|
101
|
+
forwardedRef,
|
|
102
|
+
) {
|
|
103
|
+
// `as` supersedes `href` for what the anchor actually points at (app router).
|
|
104
|
+
const target = as == null ? href : as;
|
|
105
|
+
// basePath: prefix in-app hrefs so the rendered anchor (and the soft-nav it
|
|
106
|
+
// drives) points under the configured basePath, matching Next. addBasePath
|
|
107
|
+
// leaves external/absolute/already-prefixed hrefs untouched, and is a no-op
|
|
108
|
+
// when no basePath is configured.
|
|
109
|
+
// skipTrailingSlashRedirect preserves the author's exact trailing slash (both
|
|
110
|
+
// the rendered anchor and the soft-nav it drives); otherwise normalize per
|
|
111
|
+
// the trailingSlash config so the anchor matches the canonical URL.
|
|
112
|
+
const hrefBase = hrefString(target);
|
|
113
|
+
const resolvedHref = addBasePath(
|
|
114
|
+
skipTrailingSlashRedirect() ? hrefBase : applyTrailingSlash(hrefBase),
|
|
115
|
+
);
|
|
116
|
+
const blocked = isJavascriptUrl(resolvedHref);
|
|
117
|
+
const prefetchMode = nextPrefetchMode(prefetch);
|
|
118
|
+
const lifecycleRef = usePrefetchLifecycle(onPrefetchStart, onPrefetchFinish);
|
|
119
|
+
const anchorRef = useComposedRef(lifecycleRef, forwardedRef);
|
|
120
|
+
const statusToken = useLinkToken();
|
|
121
|
+
|
|
122
|
+
const handleClick = (event: JSX.TargetedMouseEvent<HTMLAnchorElement>) => {
|
|
123
|
+
onClick?.(event);
|
|
124
|
+
if (event.defaultPrevented) return;
|
|
125
|
+
|
|
126
|
+
if (blocked) {
|
|
127
|
+
// Same guard the runtime applies to static links; a hydrated Link cancels
|
|
128
|
+
// its own default so the javascript: URL never runs.
|
|
129
|
+
if (blockJavascriptUrl(resolvedHref)) event.preventDefault();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Modifier/middle clicks, target, download: let the browser handle it;
|
|
134
|
+
// onNavigate does not fire (matches Next).
|
|
135
|
+
if (!isPlainLeftClick(event)) return;
|
|
136
|
+
|
|
137
|
+
if (isExternalHref(resolvedHref)) {
|
|
138
|
+
// External hrefs never soft-navigate and never fire onNavigate. `replace`
|
|
139
|
+
// still swaps history rather than pushing.
|
|
140
|
+
if (replace) {
|
|
141
|
+
event.preventDefault();
|
|
142
|
+
window.location.replace(resolvedHref);
|
|
143
|
+
}
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Internal soft navigation: onNavigate can cancel, and this Link drives the
|
|
148
|
+
// navigation itself so useLinkStatus tracks it. preventDefault keeps the
|
|
149
|
+
// router's document-level handler from navigating a second time.
|
|
150
|
+
if (onNavigate) {
|
|
151
|
+
const navEvent = navigationEvent();
|
|
152
|
+
onNavigate(navEvent);
|
|
153
|
+
if (navEvent.defaultPrevented) {
|
|
154
|
+
event.preventDefault();
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
event.preventDefault();
|
|
160
|
+
for (const type of transitionTypes ?? []) addTransitionType(type);
|
|
161
|
+
const url = new URL(resolvedHref, window.location.href);
|
|
162
|
+
const samePage =
|
|
163
|
+
url.pathname === window.location.pathname && url.search === window.location.search;
|
|
164
|
+
if (samePage && url.hash) {
|
|
165
|
+
window.location.hash = url.hash;
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
navigateWithStatus(statusToken, url.href, { replace, scroll });
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const anchorAttributes: Record<string, unknown> = {
|
|
172
|
+
...anchorProps,
|
|
173
|
+
ref: anchorRef,
|
|
174
|
+
href: resolvedHref,
|
|
175
|
+
'data-pnext-link': true,
|
|
176
|
+
'data-prefetch': prefetchMode === false ? 'false' : prefetchMode,
|
|
177
|
+
// prefetch={true} requests a FULL prefetch (5-min static window) vs the
|
|
178
|
+
// default partial/dynamic 30s window; the router reads this attribute.
|
|
179
|
+
// 'unstable_forceStale' is Next 16's alias for the full prefetch mode.
|
|
180
|
+
'data-prefetch-full': prefetch === true || prefetch === 'unstable_forceStale' ? 'true' : undefined,
|
|
181
|
+
// unstable_dynamicOnHover: hover intent upgrades the (viewport, partial)
|
|
182
|
+
// prefetch to a full one that includes the dynamic data.
|
|
183
|
+
'data-prefetch-hover-full': unstable_dynamicOnHover ? 'true' : undefined,
|
|
184
|
+
'data-pnext-replace': replace ? 'true' : undefined,
|
|
185
|
+
'data-pnext-scroll': scroll === false ? 'false' : undefined,
|
|
186
|
+
onClick: handleClick,
|
|
187
|
+
};
|
|
188
|
+
if (blocked) anchorAttributes['data-pnext-blocked-href'] = resolvedHref;
|
|
189
|
+
|
|
190
|
+
if (
|
|
191
|
+
(typeof __PNEXT_LINK_LEGACY__ === 'boolean' ? __PNEXT_LINK_LEGACY__ : true) &&
|
|
192
|
+
legacyBehavior
|
|
193
|
+
) {
|
|
194
|
+
return renderLegacyChild(
|
|
195
|
+
children,
|
|
196
|
+
resolvedHref,
|
|
197
|
+
passHref,
|
|
198
|
+
handleClick,
|
|
199
|
+
anchorAttributes,
|
|
200
|
+
__pnextLegacyChildKind,
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return createElement(
|
|
205
|
+
'a',
|
|
206
|
+
anchorAttributes,
|
|
207
|
+
createElement(LinkStatusContext.Provider, { value: { pending: linkPending(statusToken) } }, children),
|
|
208
|
+
) as ReactElement;
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
export default Link;
|
|
212
|
+
export { Link };
|
|
213
|
+
|
|
214
|
+
export function useLinkStatus() {
|
|
215
|
+
return useContext(LinkStatusContext);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// A stable per-Link identity used to key the pending navigation, plus a
|
|
219
|
+
// re-render on pending change so useLinkStatus consumers update.
|
|
220
|
+
function useLinkToken() {
|
|
221
|
+
const tokenRef = useRef<symbol | null>(null);
|
|
222
|
+
tokenRef.current ??= Symbol('pnext-link');
|
|
223
|
+
const token = tokenRef.current;
|
|
224
|
+
const [, force] = useState(0);
|
|
225
|
+
useEffect(() => subscribeLinkStatus(token, () => force(n => n + 1)), [token]);
|
|
226
|
+
return token;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// `legacyBehavior`: the single child element becomes the interactive element;
|
|
230
|
+
// with passHref it receives the resolved href. Validates child count like Next.
|
|
231
|
+
function renderLegacyChild(
|
|
232
|
+
children: ReactNode,
|
|
233
|
+
resolvedHref: string,
|
|
234
|
+
passHref: boolean | undefined,
|
|
235
|
+
onClick: (event: JSX.TargetedMouseEvent<HTMLAnchorElement>) => void,
|
|
236
|
+
anchorAttributes: Record<string, unknown>,
|
|
237
|
+
annotatedKind?: LegacyChildKind,
|
|
238
|
+
): ReactElement {
|
|
239
|
+
reportLegacyDeprecation();
|
|
240
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
241
|
+
const isDev = process.env.NODE_ENV !== 'production';
|
|
242
|
+
const isServer = !process.browser && typeof window === 'undefined';
|
|
243
|
+
|
|
244
|
+
if (children == null || children === false) {
|
|
245
|
+
if (isDev) {
|
|
246
|
+
throw validationError(
|
|
247
|
+
`No children were passed to <Link> with \`href\` of \`${resolvedHref}\` but one child is required https://nextjs.org/docs/messages/link-no-children`,
|
|
248
|
+
'E320',
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
return childrenOnlyOrThrow(children);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// A bare string/number child renders its own <a> (Next's legacy behavior).
|
|
255
|
+
if (typeof children === 'string' || typeof children === 'number') {
|
|
256
|
+
return createElement('a', { ...anchorAttributes, href: resolvedHref }, children);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const count = Children.count(children);
|
|
260
|
+
if (count > 1) {
|
|
261
|
+
if (isDev) {
|
|
262
|
+
throw validationError(
|
|
263
|
+
`Multiple children were passed to <Link> with \`href\` of \`${resolvedHref}\` but only one child is supported https://nextjs.org/docs/messages/link-multiple-children`,
|
|
264
|
+
'E266',
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
return childrenOnlyOrThrow(children);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const child = Children.only(children) as ReactElement<Record<string, unknown>>;
|
|
271
|
+
const kind =
|
|
272
|
+
annotatedKind ??
|
|
273
|
+
(isAsyncFunctionComponent(child.type)
|
|
274
|
+
? 'server-async'
|
|
275
|
+
: isLazyComponent(child.type)
|
|
276
|
+
? 'lazy'
|
|
277
|
+
: undefined);
|
|
278
|
+
if (isServer && (kind === 'server-sync' || kind === 'server-async' || kind === 'lazy')) {
|
|
279
|
+
reportUnsupportedServerChild(kind, isDev);
|
|
280
|
+
}
|
|
281
|
+
if (kind && kind !== 'server-sync') {
|
|
282
|
+
const error = unsupportedLegacyChildError();
|
|
283
|
+
if (isServer && kind === 'server-async-through-client') {
|
|
284
|
+
// Print the full trace, not just the message: the thrown error is
|
|
285
|
+
// swallowed downstream in this path, so without the ` at` frame lines
|
|
286
|
+
// the server log for this case would end at the message and the console
|
|
287
|
+
// snapshot (which slices up to the first ` at` line) would pick up the
|
|
288
|
+
// trailing blank line instead of terminating at the stack.
|
|
289
|
+
console.error(`⨯ ${error.stack ?? `Error: ${error.message}`}`);
|
|
290
|
+
}
|
|
291
|
+
throw error;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (isServer && isDev && isServerComponent(child.type)) {
|
|
295
|
+
reportUnsupportedServerChild('server-sync', isDev);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const childProps: Record<string, unknown> = {
|
|
299
|
+
onClick: (event: JSX.TargetedMouseEvent<HTMLAnchorElement>) => {
|
|
300
|
+
(child.props as { onClick?: (event: unknown) => void }).onClick?.(event);
|
|
301
|
+
onClick(event);
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
if (passHref || (child.type as unknown) === 'a') {
|
|
305
|
+
// The child becomes the anchor: give it the href plus the runtime's
|
|
306
|
+
// soft-nav markers, so navigation/prefetch work even when the child is a
|
|
307
|
+
// static (unhydrated) element and the onClick prop never runs.
|
|
308
|
+
childProps.href = resolvedHref;
|
|
309
|
+
childProps['data-pnext-link'] = anchorAttributes['data-pnext-link'];
|
|
310
|
+
childProps['data-prefetch'] = anchorAttributes['data-prefetch'];
|
|
311
|
+
childProps['data-prefetch-full'] = anchorAttributes['data-prefetch-full'];
|
|
312
|
+
childProps['data-prefetch-hover-full'] = anchorAttributes['data-prefetch-hover-full'];
|
|
313
|
+
childProps['data-pnext-replace'] = anchorAttributes['data-pnext-replace'];
|
|
314
|
+
childProps['data-pnext-scroll'] = anchorAttributes['data-pnext-scroll'];
|
|
315
|
+
}
|
|
316
|
+
return cloneElement(child, childProps);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function reportUnsupportedServerChild(kind: LegacyChildKind, isDev: boolean) {
|
|
320
|
+
const message =
|
|
321
|
+
kind === 'lazy'
|
|
322
|
+
? 'Using a Lazy Component as a direct child of `<Link legacyBehavior>` from a Server Component is not supported. If you need legacyBehavior, wrap your Lazy Component in a Client Component that renders the Link\'s `<a>` tag.'
|
|
323
|
+
: 'Using a Server Component as a direct child of `<Link legacyBehavior>` is not supported. If you need legacyBehavior, wrap your Server Component in a Client Component that renders the Link\'s `<a>` tag.';
|
|
324
|
+
console.error(isDev ? validationError(message, 'E394') : message);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function unsupportedLegacyChildError() {
|
|
328
|
+
return validationError(
|
|
329
|
+
'`<Link legacyBehavior>` received a direct child that is either a Server Component, or JSX that was loaded with React.lazy(). This is not supported. Either remove legacyBehavior, or make the direct child a Client Component that renders the Link\'s `<a>` tag.',
|
|
330
|
+
'E863',
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
let deprecationWarned = false;
|
|
335
|
+
function reportLegacyDeprecation() {
|
|
336
|
+
if (
|
|
337
|
+
deprecationWarned ||
|
|
338
|
+
(!process.browser && typeof window === 'undefined') ||
|
|
339
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
340
|
+
process.env.NODE_ENV === 'production'
|
|
341
|
+
) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
deprecationWarned = true;
|
|
345
|
+
console.error(
|
|
346
|
+
'`legacyBehavior` is deprecated and will be removed in a future release. A codemod is available to upgrade your components:\n\n' +
|
|
347
|
+
'npx @next/codemod@latest new-link .\n\n' +
|
|
348
|
+
'Learn more: https://nextjs.org/docs/app/building-your-application/upgrading/codemods#remove-a-tags-from-link-components',
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function validationError(message: string, digest: string) {
|
|
353
|
+
return Object.assign(new Error(message), { digest });
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// preact/compat's `Children.only` throws a bare string ("Children.only")
|
|
357
|
+
// instead of an Error, and its message doesn't match React's. Production
|
|
358
|
+
// builds (no dev-mode validation) fall through to native Children.only
|
|
359
|
+
// semantics for the no-children/multiple-children cases, so reproduce React's
|
|
360
|
+
// exact wording here rather than surfacing preact's own throw.
|
|
361
|
+
function childrenOnlyOrThrow(children: ReactNode): ReactElement {
|
|
362
|
+
try {
|
|
363
|
+
return Children.only(children) as ReactElement;
|
|
364
|
+
} catch {
|
|
365
|
+
throw new Error('React.Children.only expected to receive a single React element child.');
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function isAsyncFunctionComponent(type: unknown) {
|
|
370
|
+
if (typeof type !== 'function') return false;
|
|
371
|
+
const asyncFunctionName = Object.prototype.toString.call(type);
|
|
372
|
+
return asyncFunctionName === '[object AsyncFunction]';
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function isLazyComponent(type: unknown) {
|
|
376
|
+
return (
|
|
377
|
+
type !== null &&
|
|
378
|
+
typeof type === 'object' &&
|
|
379
|
+
(type as { $$typeof?: unknown }).$$typeof === Symbol.for('react.lazy')
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function isServerComponent(type: unknown) {
|
|
384
|
+
if (isClientComponent(type)) return false;
|
|
385
|
+
return (
|
|
386
|
+
typeof type === 'function' &&
|
|
387
|
+
(type as { $$typeof?: unknown }).$$typeof === undefined
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function isClientComponent(type: unknown) {
|
|
392
|
+
return (
|
|
393
|
+
(typeof type === 'object' || typeof type === 'function') &&
|
|
394
|
+
(type as { $$typeof?: unknown }).$$typeof === reactClientReferenceSymbol
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function useComposedRef(
|
|
399
|
+
internalRef: MutableRefObject<HTMLAnchorElement | null>,
|
|
400
|
+
userRef: Ref<HTMLAnchorElement> | undefined,
|
|
401
|
+
) {
|
|
402
|
+
return (node: HTMLAnchorElement | null) => {
|
|
403
|
+
internalRef.current = node;
|
|
404
|
+
assignRef(userRef, node);
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function assignRef(ref: Ref<HTMLAnchorElement> | undefined, node: HTMLAnchorElement | null) {
|
|
409
|
+
if (!ref) return;
|
|
410
|
+
if (typeof ref === 'function') {
|
|
411
|
+
// Callback refs may return a cleanup; Preact ignores it, which is fine —
|
|
412
|
+
// the ref is re-run with null on unmount, matching the manual-cleanup path.
|
|
413
|
+
ref(node);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
(ref as MutableRefObject<HTMLAnchorElement | null>).current = node;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function hrefString(href: string | URL | UrlObject) {
|
|
420
|
+
if (typeof href === 'string') return href;
|
|
421
|
+
if (href instanceof URL) return href.toString();
|
|
422
|
+
return routeHref(href.pathname ?? '', { search: href.query, hash: href.hash });
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function nextPrefetchMode(
|
|
426
|
+
prefetch: boolean | 'auto' | 'unstable_forceStale' | null,
|
|
427
|
+
): PrefetchMode {
|
|
428
|
+
// Next's default prefetches when the link enters the viewport; `false` opts
|
|
429
|
+
// out entirely. (`true`/'auto'/'unstable_forceStale' also map to viewport
|
|
430
|
+
// prefetch here — 'auto' is an explicit alias for the default.)
|
|
431
|
+
return prefetch === false ? false : 'visible';
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function isExternalHref(href: string) {
|
|
435
|
+
if (!process.browser && typeof window === 'undefined') return false;
|
|
436
|
+
try {
|
|
437
|
+
return new URL(href, window.location.href).origin !== window.location.origin;
|
|
438
|
+
} catch {
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function navigationEvent() {
|
|
444
|
+
return {
|
|
445
|
+
defaultPrevented: false,
|
|
446
|
+
preventDefault() {
|
|
447
|
+
this.defaultPrevented = true;
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function isPlainLeftClick(event: JSX.TargetedMouseEvent<HTMLAnchorElement>) {
|
|
453
|
+
return (
|
|
454
|
+
event.button === 0 &&
|
|
455
|
+
!event.altKey &&
|
|
456
|
+
!event.ctrlKey &&
|
|
457
|
+
!event.metaKey &&
|
|
458
|
+
!event.shiftKey &&
|
|
459
|
+
isSelfTarget(event.currentTarget.target) &&
|
|
460
|
+
!event.currentTarget.hasAttribute('download')
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function isSelfTarget(target: string) {
|
|
465
|
+
return target === '' || target === '_self';
|
|
466
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const router = {
|
|
2
|
+
push: () => undefined, replace: () => undefined, prefetch: () => undefined,
|
|
3
|
+
refresh: () => undefined, back: () => undefined, forward: () => undefined,
|
|
4
|
+
bfcacheId: '',
|
|
5
|
+
}
|
|
6
|
+
const noop = () => undefined
|
|
7
|
+
|
|
8
|
+
exports.RedirectType = { push: 'push', replace: 'replace' }
|
|
9
|
+
exports.redirect = noop
|
|
10
|
+
exports.permanentRedirect = noop
|
|
11
|
+
exports.notFound = noop
|
|
12
|
+
exports.forbidden = noop
|
|
13
|
+
exports.unauthorized = noop
|
|
14
|
+
exports.useRouter = () => router
|
|
15
|
+
exports.usePathname = () => null
|
|
16
|
+
exports.useSearchParams = () => new URLSearchParams()
|
|
17
|
+
exports.useParams = () => ({})
|
|
18
|
+
exports.useRoute = () => ({ route: null, pathname: '', params: {}, searchParams: new URLSearchParams() })
|
|
19
|
+
exports.useSelectedLayoutSegment = () => null
|
|
20
|
+
exports.useSelectedLayoutSegments = () => []
|
|
21
|
+
exports.useLinkStatus = () => ({ pending: false })
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
export {
|
|
2
|
+
forbidden,
|
|
3
|
+
notFound,
|
|
4
|
+
PNextForbiddenError,
|
|
5
|
+
PNextNotFoundError,
|
|
6
|
+
PNextRedirectError,
|
|
7
|
+
PNextUnauthorizedError,
|
|
8
|
+
unauthorized,
|
|
9
|
+
} from '../../api/navigation';
|
|
10
|
+
import {
|
|
11
|
+
permanentRedirect as pnextPermanentRedirect,
|
|
12
|
+
PNextForbiddenError,
|
|
13
|
+
PNextNotFoundError,
|
|
14
|
+
PNextRedirectError,
|
|
15
|
+
PNextUnauthorizedError,
|
|
16
|
+
ReadonlyURLSearchParams,
|
|
17
|
+
redirect as pnextRedirect,
|
|
18
|
+
} from '../../api/navigation';
|
|
19
|
+
import { isPostpone } from '../../ppr-postpone';
|
|
20
|
+
import { currentParams, currentRequest } from '../../request/context';
|
|
21
|
+
import { useContext } from 'preact/hooks';
|
|
22
|
+
import {
|
|
23
|
+
currentLayoutSegments,
|
|
24
|
+
currentParamsSnapshot,
|
|
25
|
+
hasLayoutSegmentScope,
|
|
26
|
+
} from '../../render/slots';
|
|
27
|
+
import { LayoutSegmentContext, RouteParamsContext } from '../../render/island-context';
|
|
28
|
+
import { canonicalUrlHref } from './canonical-url';
|
|
29
|
+
import { isUnrecognizedActionError } from '../actions/unrecognized-error';
|
|
30
|
+
import { stripBasePath } from '../client/base-path';
|
|
31
|
+
import { addBasePath } from '../client/base-path';
|
|
32
|
+
import type { CurrentRoute, PNextRouter } from '../../api/client-navigation';
|
|
33
|
+
import type { RouteParams, RoutePath } from '../../types';
|
|
34
|
+
|
|
35
|
+
export type { RedirectStatus } from '../../api/navigation';
|
|
36
|
+
export type { CurrentRoute, PNextRouter } from '../../api/client-navigation';
|
|
37
|
+
export { ReadonlyURLSearchParams };
|
|
38
|
+
|
|
39
|
+
export const RedirectType = {
|
|
40
|
+
push: 'push',
|
|
41
|
+
replace: 'replace',
|
|
42
|
+
} as const;
|
|
43
|
+
|
|
44
|
+
export type RedirectType = (typeof RedirectType)[keyof typeof RedirectType];
|
|
45
|
+
|
|
46
|
+
export function redirect(location: string, type?: RedirectType): never {
|
|
47
|
+
try {
|
|
48
|
+
// Next prefixes a root-relative redirect target with basePath; absolute URLs (including same-host
|
|
49
|
+
// targets outside the basePath) are left as-is. Normalizing here means every downstream consumer -
|
|
50
|
+
// the page-GET redirect response and the server-action redirect envelope - sees a basePath-space
|
|
51
|
+
// location uniformly.
|
|
52
|
+
pnextRedirect(addBasePath(location));
|
|
53
|
+
} catch (error) {
|
|
54
|
+
// Server actions honor push/replace history semantics on the client;
|
|
55
|
+
// carry the requested type on the control-flow error.
|
|
56
|
+
if (type && error instanceof PNextRedirectError) {
|
|
57
|
+
(error as PNextRedirectError & { redirectType?: RedirectType }).redirectType = type;
|
|
58
|
+
}
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function permanentRedirect(location: string, _type?: RedirectType): never {
|
|
64
|
+
pnextPermanentRedirect(addBasePath(location));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Rethrow framework control-flow errors so user catch blocks don't swallow them. */
|
|
68
|
+
export function unstable_rethrow(error: unknown): void {
|
|
69
|
+
if (
|
|
70
|
+
error instanceof PNextRedirectError ||
|
|
71
|
+
error instanceof PNextNotFoundError ||
|
|
72
|
+
error instanceof PNextForbiddenError ||
|
|
73
|
+
error instanceof PNextUnauthorizedError ||
|
|
74
|
+
isPostpone(error)
|
|
75
|
+
) {
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const serverRouter: PNextRouter = {
|
|
81
|
+
push: () => undefined,
|
|
82
|
+
replace: () => undefined,
|
|
83
|
+
prefetch: () => undefined,
|
|
84
|
+
refresh: () => undefined,
|
|
85
|
+
back: () => undefined,
|
|
86
|
+
forward: () => undefined,
|
|
87
|
+
bfcacheId: '',
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export function useRouter(): PNextRouter {
|
|
91
|
+
return serverRouter;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function usePathname() {
|
|
95
|
+
return stripBasePath(currentUrl().pathname);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function useSearchParams(): ReadonlyURLSearchParams {
|
|
99
|
+
return new ReadonlyURLSearchParams(currentUrl().search);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function useParams<Route extends RoutePath = RoutePath>() {
|
|
103
|
+
return resolveParams() as RouteParams<Route>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* A SERVER component inside a parallel-route slot runs within the slot's params scope (route params plus
|
|
108
|
+
* the slot's own dynamic/catch-all captures), so read it directly. A `'use client'` component in a slot
|
|
109
|
+
* is SSR'd as an island, rendered to string OUTSIDE that scope, so it falls back to the
|
|
110
|
+
* RouteParamsContext the renderer provides around the island - matching what the client re-provides at
|
|
111
|
+
* mount. Otherwise the global route params apply. useContext is safe because the context branch only
|
|
112
|
+
* runs during a preact render.
|
|
113
|
+
*/
|
|
114
|
+
function resolveParams(): Record<string, string | string[]> {
|
|
115
|
+
const scope = currentParamsSnapshot();
|
|
116
|
+
if (scope) return scope;
|
|
117
|
+
// Island SSR (a `'use client'` component in a slot) reads the snapshot from
|
|
118
|
+
// the RouteParamsContext the renderer wraps around it. useContext is only
|
|
119
|
+
// valid inside a preact render; a plain server component resolved by direct
|
|
120
|
+
// call has no hook context, so guard it and fall back to the global params.
|
|
121
|
+
try {
|
|
122
|
+
const snapshot = useContext(RouteParamsContext);
|
|
123
|
+
if (snapshot) return snapshot;
|
|
124
|
+
} catch {
|
|
125
|
+
// not in a preact render — fall through to global route params
|
|
126
|
+
}
|
|
127
|
+
return currentParams();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function useRoute<Route extends RoutePath = RoutePath>(): CurrentRoute<Route> {
|
|
131
|
+
const url = currentUrl();
|
|
132
|
+
return {
|
|
133
|
+
route: null,
|
|
134
|
+
pathname: url.pathname,
|
|
135
|
+
params: resolveParams() as RouteParams<Route>,
|
|
136
|
+
searchParams: new ReadonlyURLSearchParams(url.search),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function useSelectedLayoutSegment(
|
|
141
|
+
parallelRoutesKey?: string,
|
|
142
|
+
): string | null {
|
|
143
|
+
const segments = selectedLayoutSegments(parallelRoutesKey);
|
|
144
|
+
return segments[parallelRoutesKey ? segments.length - 1 : 0] ?? null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function useSelectedLayoutSegments(parallelRoutesKey?: string): string[] {
|
|
148
|
+
// A defensive copy so callers can't mutate the render-scoped array.
|
|
149
|
+
return [...selectedLayoutSegments(parallelRoutesKey)];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* A SERVER component in a layout runs inside the layout-segment scope, so read it directly. A
|
|
154
|
+
* `'use client'` layout is SSR'd as an island, rendered to string OUTSIDE that scope, so it falls back
|
|
155
|
+
* to the LayoutSegmentContext the renderer provides around the island - matching what the client
|
|
156
|
+
* re-provides at mount. useContext is safe: the no-scope branch only runs during a preact render.
|
|
157
|
+
*/
|
|
158
|
+
function selectedLayoutSegments(parallelRoutesKey?: string): string[] {
|
|
159
|
+
if (hasLayoutSegmentScope()) return currentLayoutSegments(parallelRoutesKey);
|
|
160
|
+
const snapshot = useContext(LayoutSegmentContext);
|
|
161
|
+
if (!snapshot) return [];
|
|
162
|
+
return parallelRoutesKey ? (snapshot.slots[parallelRoutesKey] ?? []) : snapshot.segments;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function useLinkStatus() {
|
|
166
|
+
return { pending: false };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The canonical (as-requested) URL for the active render. After a rewrite the
|
|
171
|
+
* request URL is the destination, so hooks that must reflect what the browser
|
|
172
|
+
* asked for read the recorded canonical URL first (see canonical-url.ts).
|
|
173
|
+
*/
|
|
174
|
+
function currentUrl() {
|
|
175
|
+
return new URL(canonicalUrlHref() ?? currentRequest()?.url ?? 'http://pnext.local/');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* True for the error a server-action dispatch throws when the server no longer
|
|
180
|
+
* recognizes the action id (version skew). Matches the message the pnext
|
|
181
|
+
* action endpoint sends for an unknown id.
|
|
182
|
+
*/
|
|
183
|
+
export function unstable_isUnrecognizedActionError(error: unknown): boolean {
|
|
184
|
+
return isUnrecognizedActionError(error);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// CSS-in-JS SSR insertion hook (Next exports it from next/navigation).
|
|
188
|
+
export { useServerInsertedHTML } from '../react/server-inserted-html';
|