fix: synchronize TechLog focus and not-found runtime
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
|
||||
import type {
|
||||
@@ -28,23 +28,30 @@ export function HomeFocus({
|
||||
const keys = items.map((item) => item.key);
|
||||
const activeItem = items.find((item) => item.key === activeKey) ?? items[0];
|
||||
|
||||
function replaceFocus(key: FocusKey) {
|
||||
const search = new URLSearchParams(location.search);
|
||||
search.set("focus", key);
|
||||
void navigate(
|
||||
{
|
||||
pathname: location.pathname,
|
||||
search: `?${search.toString()}`,
|
||||
hash: location.hash,
|
||||
},
|
||||
{ replace: true },
|
||||
);
|
||||
}
|
||||
const replaceFocus = useCallback(
|
||||
(key: FocusKey) => {
|
||||
const search = new URLSearchParams(location.search);
|
||||
search.set("focus", key);
|
||||
void navigate(
|
||||
{
|
||||
pathname: location.pathname,
|
||||
search: `?${search.toString()}`,
|
||||
hash: location.hash,
|
||||
},
|
||||
{ replace: true },
|
||||
);
|
||||
},
|
||||
[location.hash, location.pathname, location.search, navigate],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!shouldNormalizeFocusUrl(requestedKey, activeKey)) return;
|
||||
replaceFocus(activeKey);
|
||||
});
|
||||
setActiveKey(initialKey);
|
||||
}, [initialKey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!shouldNormalizeFocusUrl(requestedKey, initialKey)) return;
|
||||
replaceFocus(initialKey);
|
||||
}, [initialKey, replaceFocus, requestedKey]);
|
||||
|
||||
function select(key: FocusKey, moveKeyboardFocus = false) {
|
||||
setActiveKey(key);
|
||||
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
import type { ParsedRouteInput } from "./route-contract.ts";
|
||||
import {
|
||||
RegisteredNotFoundProvider,
|
||||
type RegisteredNotFoundDescriptor,
|
||||
RouteInputProvider,
|
||||
} from "./route-input.tsx";
|
||||
|
||||
@@ -259,14 +260,14 @@ function RegisteredRoute<RouteIdValue extends string>({
|
||||
definition,
|
||||
runtime,
|
||||
codecs,
|
||||
NotFoundComponent,
|
||||
registeredNotFound,
|
||||
}: {
|
||||
routeId: RouteIdValue;
|
||||
buildId: string;
|
||||
definition: RouteDefinition;
|
||||
runtime: GroupedRouteRuntimeDefinition;
|
||||
codecs: RouteCodecRegistry;
|
||||
NotFoundComponent?: ComponentType;
|
||||
registeredNotFound?: RegisteredNotFoundRegistration;
|
||||
}) {
|
||||
const params = useParams();
|
||||
const [search] = useSearchParams();
|
||||
@@ -282,10 +283,35 @@ function RegisteredRoute<RouteIdValue extends string>({
|
||||
if (!parsed.success) return <InvalidRouteSurface code={parsed.code} />;
|
||||
const routeInput = parsed.data;
|
||||
const RuntimeComponent = runtime.Component;
|
||||
const notFoundDescriptor: RegisteredNotFoundDescriptor | undefined =
|
||||
registeredNotFound
|
||||
? {
|
||||
...registeredNotFound,
|
||||
render: () => {
|
||||
const NotFoundRuntime = registeredNotFound.runtime.Component;
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
<RouteLoadingSurface
|
||||
definition={registeredNotFound.definition}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ChunkRecoveryBoundary
|
||||
chunkId={registeredNotFound.definition.chunkId}
|
||||
recover={recovery.recoverChunk}
|
||||
>
|
||||
<NotFoundRuntime />
|
||||
</ChunkRecoveryBoundary>
|
||||
</Suspense>
|
||||
);
|
||||
},
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const content = (
|
||||
<RouteInputProvider input={routeInput}>
|
||||
<RegisteredNotFoundProvider Component={NotFoundComponent}>
|
||||
<RegisteredNotFoundProvider descriptor={notFoundDescriptor}>
|
||||
<CanonicalRouteRedirect
|
||||
input={routeInput}
|
||||
definition={definition}
|
||||
@@ -329,6 +355,12 @@ type GroupedRouteRuntimeDefinition = Readonly<{
|
||||
Component: ComponentType;
|
||||
}>;
|
||||
|
||||
type RegisteredNotFoundRegistration = Readonly<{
|
||||
definition: RouteDefinition;
|
||||
runtime: GroupedRouteRuntimeDefinition;
|
||||
buildId: string;
|
||||
}>;
|
||||
|
||||
type GroupedRouteRegistry = Readonly<Record<string, RouteDefinition>>;
|
||||
type GroupedRouteRuntime = Readonly<
|
||||
Record<string, GroupedRouteRuntimeDefinition>
|
||||
@@ -352,7 +384,16 @@ export function createGroupedRouteObjects(
|
||||
PUBLIC: [],
|
||||
STUDIO: [],
|
||||
};
|
||||
const NotFoundComponent = runtime.NOT_FOUND?.Component;
|
||||
const notFoundDefinition = registry.NOT_FOUND;
|
||||
const notFoundRuntime = runtime.NOT_FOUND;
|
||||
const registeredNotFound =
|
||||
notFoundDefinition && notFoundRuntime
|
||||
? {
|
||||
definition: notFoundDefinition,
|
||||
runtime: notFoundRuntime,
|
||||
buildId,
|
||||
}
|
||||
: undefined;
|
||||
for (const definition of Object.values(registry)) {
|
||||
const routeId = definition.routeId;
|
||||
const routeRuntime = runtime[definition.routeId];
|
||||
@@ -366,7 +407,11 @@ export function createGroupedRouteObjects(
|
||||
definition={definition}
|
||||
runtime={routeRuntime}
|
||||
codecs={codecs}
|
||||
NotFoundComponent={NotFoundComponent}
|
||||
registeredNotFound={
|
||||
definition.layoutGroup === notFoundDefinition?.layoutGroup
|
||||
? registeredNotFound
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
if (definition.path === "/") {
|
||||
|
||||
@@ -5,10 +5,23 @@ import {
|
||||
useContext,
|
||||
} from "react";
|
||||
|
||||
import type { RouteDefinition } from "../../contracts/routes.ts";
|
||||
import type { ParsedRouteInput, RouteId } from "./route-contract.ts";
|
||||
|
||||
const RouteInputContext = createContext<ParsedRouteInput<string> | null>(null);
|
||||
const RegisteredNotFoundContext = createContext<ComponentType | null>(null);
|
||||
|
||||
export type RegisteredNotFoundDescriptor = Readonly<{
|
||||
definition: RouteDefinition;
|
||||
runtime: Readonly<{
|
||||
moduleId: string;
|
||||
Component: ComponentType;
|
||||
}>;
|
||||
buildId: string;
|
||||
render(): ReactNode;
|
||||
}>;
|
||||
|
||||
const RegisteredNotFoundContext =
|
||||
createContext<RegisteredNotFoundDescriptor | null>(null);
|
||||
|
||||
export function RouteInputProvider<RouteIdValue extends string>({
|
||||
input,
|
||||
@@ -33,21 +46,23 @@ export function useRouteInput<
|
||||
}
|
||||
|
||||
export function RegisteredNotFoundProvider({
|
||||
Component,
|
||||
descriptor,
|
||||
children,
|
||||
}: Readonly<{
|
||||
Component?: ComponentType;
|
||||
descriptor?: RegisteredNotFoundDescriptor;
|
||||
children: ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<RegisteredNotFoundContext.Provider value={Component ?? null}>
|
||||
<RegisteredNotFoundContext.Provider value={descriptor ?? null}>
|
||||
{children}
|
||||
</RegisteredNotFoundContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function RegisteredNotFoundRoute() {
|
||||
const Component = useContext(RegisteredNotFoundContext);
|
||||
if (!Component) throw new Error("Registered not-found runtime is required");
|
||||
return <Component />;
|
||||
const descriptor = useContext(RegisteredNotFoundContext);
|
||||
if (!descriptor) {
|
||||
throw new Error("Registered not-found runtime is required");
|
||||
}
|
||||
return descriptor.render();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user