netjes 0.0.2 → 0.0.4
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 +13 -11
- package/cli/agents.mjs +25 -2
- package/cli/commands.mjs +56 -6
- package/cli/help.mjs +7 -2
- package/cli/info.mjs +275 -33
- package/cli/routes.mjs +35 -8
- package/cli/scaffold.mjs +31 -4
- package/dist/actions.d.ts +7 -2
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +9 -3
- package/dist/actions.js.map +1 -1
- package/dist/client-entry.d.ts +1 -0
- package/dist/client-entry.d.ts.map +1 -1
- package/dist/client-entry.js +19 -2
- package/dist/client-entry.js.map +1 -1
- package/dist/deferred.d.ts +11 -0
- package/dist/deferred.d.ts.map +1 -1
- package/dist/deferred.js +15 -1
- package/dist/deferred.js.map +1 -1
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +10 -1
- package/dist/document.js.map +1 -1
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +11 -0
- package/dist/errors.js.map +1 -0
- package/dist/form.d.ts +5 -5
- package/dist/form.d.ts.map +1 -1
- package/dist/form.js +5 -2
- package/dist/form.js.map +1 -1
- package/dist/href.d.ts +31 -3
- package/dist/href.d.ts.map +1 -1
- package/dist/href.js +131 -10
- package/dist/href.js.map +1 -1
- package/dist/image.d.ts +3 -3
- package/dist/image.d.ts.map +1 -1
- package/dist/image.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/link.d.ts +15 -7
- package/dist/link.d.ts.map +1 -1
- package/dist/link.js +38 -7
- package/dist/link.js.map +1 -1
- package/dist/metadata.d.ts +8 -2
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +14 -5
- package/dist/metadata.js.map +1 -1
- package/dist/modules.d.ts +15 -1
- package/dist/modules.d.ts.map +1 -1
- package/dist/modules.js.map +1 -1
- package/dist/mounted.d.ts +55 -0
- package/dist/mounted.d.ts.map +1 -0
- package/dist/mounted.js +110 -0
- package/dist/mounted.js.map +1 -0
- package/dist/navigation-focus.d.ts +38 -0
- package/dist/navigation-focus.d.ts.map +1 -0
- package/dist/navigation-focus.js +71 -0
- package/dist/navigation-focus.js.map +1 -0
- package/dist/payload.d.ts +8 -0
- package/dist/payload.d.ts.map +1 -1
- package/dist/payload.js +28 -0
- package/dist/payload.js.map +1 -1
- package/dist/query.d.ts +5 -2
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +11 -8
- package/dist/query.js.map +1 -1
- package/dist/render-document.d.ts +74 -0
- package/dist/render-document.d.ts.map +1 -0
- package/dist/render-document.js +393 -0
- package/dist/render-document.js.map +1 -0
- package/dist/route-announcer.d.ts +18 -0
- package/dist/route-announcer.d.ts.map +1 -0
- package/dist/route-announcer.js +58 -0
- package/dist/route-announcer.js.map +1 -0
- package/dist/route-tree.d.ts +6 -2
- package/dist/route-tree.d.ts.map +1 -1
- package/dist/route-tree.js +17 -7
- package/dist/route-tree.js.map +1 -1
- package/dist/router.d.ts +28 -5
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +71 -26
- package/dist/router.js.map +1 -1
- package/dist/script.d.ts +28 -5
- package/dist/script.d.ts.map +1 -1
- package/dist/script.js +192 -21
- package/dist/script.js.map +1 -1
- package/dist/server-render.d.ts.map +1 -1
- package/dist/server-render.js +4 -1
- package/dist/server-render.js.map +1 -1
- package/dist/testing-vitest.d.ts +8 -0
- package/dist/testing-vitest.d.ts.map +1 -0
- package/dist/testing-vitest.js +26 -0
- package/dist/testing-vitest.js.map +1 -0
- package/dist/testing.d.ts +17 -1
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +18 -3
- package/dist/testing.js.map +1 -1
- package/dist/zod-jitless.d.ts +2 -0
- package/dist/zod-jitless.d.ts.map +1 -0
- package/dist/zod-jitless.js +4 -0
- package/dist/zod-jitless.js.map +1 -0
- package/package.json +1 -1
- package/sidecar/server.mjs +109 -15
- package/skills/netjes/SKILL.md +15 -7
- package/skills/netjes/references/caching-and-streaming.md +37 -4
- package/skills/netjes/references/data-and-actions.md +15 -7
- package/skills/netjes/references/deployment.md +7 -3
- package/skills/netjes/references/routing.md +32 -9
- package/skills/netjes/references/testing.md +23 -7
- package/src/actions.tsx +9 -3
- package/src/client-entry.tsx +20 -2
- package/src/deferred.ts +14 -1
- package/src/document.tsx +22 -3
- package/src/errors.ts +12 -0
- package/src/form.tsx +10 -7
- package/src/href.ts +137 -11
- package/src/image.tsx +3 -3
- package/src/index.ts +19 -1
- package/src/link.tsx +47 -13
- package/src/metadata.tsx +19 -6
- package/src/modules.ts +15 -2
- package/src/mounted.tsx +160 -0
- package/src/navigation-focus.ts +75 -0
- package/src/payload.ts +32 -0
- package/src/query.tsx +14 -8
- package/src/render-document.tsx +483 -0
- package/src/route-announcer.tsx +69 -0
- package/src/route-tree.tsx +17 -6
- package/src/router.tsx +116 -31
- package/src/script.tsx +214 -23
- package/src/server-render.tsx +4 -1
- package/src/testing-vitest.ts +37 -0
- package/src/testing.tsx +38 -5
- package/src/virtual.d.ts +8 -1
- package/src/zod-jitless.ts +15 -0
- package/template/.dockerignore +3 -0
- package/template/Dockerfile +11 -1
- package/template/README.md +11 -7
- package/template/_AGENTS.md +38 -9
- package/template/__NAME__.csproj +2 -0
- package/template/app/document.test.ts +29 -0
- package/template/app/error.tsx +3 -2
- package/template/app/layout.tsx +10 -1
- package/template/docker-compose.override.yml +4 -3
- package/template/docker-compose.prod.yml +2 -1
- package/template/e2e/home.spec.ts +32 -0
- package/template/generated/netjes.ts +43 -14
- package/template/package.json +4 -1
- package/template/playwright.config.ts +36 -0
- package/template/tests/__NAME__.Tests/AppFactory.cs +31 -0
- package/template/tests/__NAME__.Tests/HomePageTests.cs +55 -0
- package/template/tests/__NAME__.Tests/__NAME__.Tests.csproj +30 -0
- package/template/tsconfig.app.json +20 -0
- package/template/tsconfig.json +2 -17
- package/template/tsconfig.node.json +8 -0
- package/vite/packages.mjs +345 -0
- package/vite/plugin.d.ts +16 -1
- package/vite/plugin.mjs +224 -34
package/dist/route-tree.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Component, type ReactElement, type ReactNode } from 'react';
|
|
2
2
|
import { type Metadata } from './metadata.tsx';
|
|
3
|
-
import type { RouteModules } from './modules.ts';
|
|
3
|
+
import type { ErrorPageProps, RouteModules } from './modules.ts';
|
|
4
4
|
import type { PageKind, Params, SegmentRef } from './payload.ts';
|
|
5
5
|
export type { ErrorPageProps } from './modules.ts';
|
|
6
6
|
type RouteTreeProps = {
|
|
@@ -15,6 +15,8 @@ type RouteTreeProps = {
|
|
|
15
15
|
pageKey: string;
|
|
16
16
|
/** What `reset` does on an error page the server chose: load the page again. */
|
|
17
17
|
retry?: () => void;
|
|
18
|
+
/** The error an error page (`pageKind` error) gets in place of the one the server's props describe: one caught here. */
|
|
19
|
+
error?: ErrorPageProps['error'];
|
|
18
20
|
/** Metadata from the C# loader. */
|
|
19
21
|
metadata?: Metadata | undefined;
|
|
20
22
|
};
|
|
@@ -42,12 +44,14 @@ export declare class ErrorBoundary extends Component<BoundaryProps, {
|
|
|
42
44
|
private readonly reset;
|
|
43
45
|
render(): ReactNode;
|
|
44
46
|
}
|
|
47
|
+
/** What a boundary caught, as the Error `error.tsx` gets: an Error as it is, a string as its message, else a stand-in. */
|
|
48
|
+
export declare function asError(error: unknown): ErrorPageProps['error'];
|
|
45
49
|
/**
|
|
46
50
|
* The page wrapped in its segments, outermost first. Per segment, as in the Next.js App Router:
|
|
47
51
|
* layout > template > error boundary > Suspense (loading) > the next segment or the page. Layouts keep their state
|
|
48
52
|
* while the page underneath changes; templates remount.
|
|
49
53
|
*/
|
|
50
|
-
export declare function RouteTree({ modules, segments, props, params, status, pageKind, pageKey, retry, metadata }: RouteTreeProps): ReactElement;
|
|
54
|
+
export declare function RouteTree({ modules, segments, props, params, status, pageKind, pageKey, retry, metadata, error }: RouteTreeProps): ReactElement;
|
|
51
55
|
/** Shown for 404s and errors when the app has no not-found or error page of its own. */
|
|
52
56
|
export declare function BuiltInPage({ status, props }: {
|
|
53
57
|
status: number;
|
package/dist/route-tree.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-tree.d.ts","sourceRoot":"","sources":["../src/route-tree.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,YAAY,EAAE,KAAK,SAAS,EAA2B,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"route-tree.d.ts","sourceRoot":"","sources":["../src/route-tree.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,YAAY,EAAE,KAAK,SAAS,EAA2B,MAAM,OAAO,CAAC;AAE9F,OAAO,EAAE,KAAK,QAAQ,EAAiC,MAAM,gBAAgB,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAiB,YAAY,EAAE,MAAM,cAAc,CAAC;AAChF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAEjE,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,KAAK,cAAc,GAAG;IACpB,OAAO,EAAE,YAAY,CAAC;IACtB,iGAAiG;IACjG,QAAQ,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IACjC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAChC,+GAA+G;IAC/G,OAAO,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,wHAAwH;IACxH,KAAK,CAAC,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC;IAChC,mCAAmC;IACnC,QAAQ,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;CACjC,CAAC;AAEF,2GAA2G;AAC3G,eAAO,MAAM,kBAAkB,iCAAmB,CAAC;AAUnD,KAAK,aAAa,GAAG;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,IAAI,KAAK,SAAS,CAAC;IAAC,QAAQ,EAAE,SAAS,CAAA;CAAE,CAAC;AAE5H,0GAA0G;AAC1G,qBAAa,aAAc,SAAQ,SAAS,CAAC,aAAa,EAAE;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC;IACrF,KAAK;QAAK,KAAK,EAAU,OAAO;QAAE,MAAM;MAAU;IAE3D,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,OAAO;;;MAE7C;IAEQ,kBAAkB,CAAC,QAAQ,EAAE,aAAa,QAElD;IAED,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuD;IAEpE,MAAM,cAEd;CACF;AAED,0HAA0H;AAC1H,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,CAG/D;AAgBD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,EAAE,OAAO,EAAE,QAAa,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,cAAc,GAAG,YAAY,CAoCpJ;AAWD,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,+BAkBhG"}
|
package/dist/route-tree.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Component, Suspense, createContext } from 'react';
|
|
3
|
+
import { withheldError } from './errors.js';
|
|
3
4
|
import { MetadataTags, resolveMetadata } from './metadata.js';
|
|
4
5
|
/** How many URL segments the layout rendering here covers: what useSelectedLayoutSegment(s) count from. */
|
|
5
6
|
export const LayoutDepthContext = createContext(0);
|
|
@@ -25,16 +26,25 @@ export class ErrorBoundary extends Component {
|
|
|
25
26
|
return this.state.failed ? this.props.fallback(this.state.error, this.reset) : this.props.children;
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
/** What a boundary caught, as the Error `error.tsx` gets: an Error as it is, a string as its message, else a stand-in. */
|
|
30
|
+
export function asError(error) {
|
|
31
|
+
if (error instanceof Error)
|
|
32
|
+
return error;
|
|
33
|
+
return typeof error === 'string' ? new Error(error) : withheldError();
|
|
30
34
|
}
|
|
31
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* The error the server reported (props of the error page it rendered), as an Error for `error.tsx`: marked withheld
|
|
37
|
+
* when the server says its message stands in for the exception's, and with the server's stack or none, never the
|
|
38
|
+
* stack of the code here that built it.
|
|
39
|
+
*/
|
|
32
40
|
function serverError(props) {
|
|
33
41
|
const error = new Error(typeof props.message === 'string' ? props.message : 'Something went wrong');
|
|
34
42
|
if (typeof props.status === 'number')
|
|
35
43
|
error.status = props.status;
|
|
36
|
-
if (
|
|
37
|
-
error.
|
|
44
|
+
if (props.withheld === true)
|
|
45
|
+
error.withheld = true;
|
|
46
|
+
// Defined rather than assigned, as an own property: Firefox keeps the stack in an accessor on Error.prototype.
|
|
47
|
+
Object.defineProperty(error, 'stack', { value: typeof props.stack === 'string' ? props.stack : undefined, writable: true, configurable: true });
|
|
38
48
|
return error;
|
|
39
49
|
}
|
|
40
50
|
/**
|
|
@@ -42,9 +52,9 @@ function serverError(props) {
|
|
|
42
52
|
* layout > template > error boundary > Suspense (loading) > the next segment or the page. Layouts keep their state
|
|
43
53
|
* while the page underneath changes; templates remount.
|
|
44
54
|
*/
|
|
45
|
-
export function RouteTree({ modules, segments = [], props, params, status, pageKind, pageKey, retry, metadata }) {
|
|
55
|
+
export function RouteTree({ modules, segments = [], props, params, status, pageKind, pageKey, retry, metadata, error }) {
|
|
46
56
|
const Page = modules.page;
|
|
47
|
-
const pageProps = pageKind === 'error' ? { ...props, error: serverError(props), reset: retry ?? (() => { }) } : props;
|
|
57
|
+
const pageProps = pageKind === 'error' ? { ...props, error: error ?? serverError(props), reset: retry ?? (() => { }) } : props;
|
|
48
58
|
let element = Page ? _jsx(Page, { ...pageProps }, pageKey) : _jsx(BuiltInPage, { status: status, props: props }, pageKey);
|
|
49
59
|
for (let i = modules.segments.length - 1; i >= 0; i--) {
|
|
50
60
|
const { Layout, Template, Loading, Error: SegmentError, slots } = modules.segments[i];
|
package/dist/route-tree.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-tree.js","sourceRoot":"","sources":["../src/route-tree.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAqC,QAAQ,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAC9F,OAAO,EAAiB,YAAY,EAAE,eAAe,EAAE,MAAM,eAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"route-tree.js","sourceRoot":"","sources":["../src/route-tree.tsx"],"names":[],"mappings":"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"}
|
package/dist/router.d.ts
CHANGED
|
@@ -19,15 +19,23 @@ export type NavigationState = {
|
|
|
19
19
|
};
|
|
20
20
|
/** The client router that `useRouter()` returns. */
|
|
21
21
|
export type Router = {
|
|
22
|
+
/**
|
|
23
|
+
* Navigates, adding a history entry. The promise resolves once the router has handed the page to React, which can be
|
|
24
|
+
* before React shows it, or once the navigation has ended another way.
|
|
25
|
+
*/
|
|
22
26
|
push(href: Href, options?: {
|
|
23
27
|
scroll?: boolean;
|
|
24
28
|
}): Promise<void>;
|
|
29
|
+
/** Navigates, replacing the history entry. The promise resolves as `push`'s does. */
|
|
25
30
|
replace(href: Href, options?: {
|
|
26
31
|
scroll?: boolean;
|
|
27
32
|
}): Promise<void>;
|
|
28
|
-
/** Fetches the page payload and modules ahead of a navigation. Links do this on hover and
|
|
33
|
+
/** Fetches the page payload and modules ahead of a navigation. Links do this on hover, focus and touch. */
|
|
29
34
|
prefetch(href: Href): void;
|
|
30
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* Runs every loader of the page on screen again, layout loaders included, and renders the page with the new props, in
|
|
37
|
+
* the same history entry and without scrolling. The promise resolves as `push`'s does.
|
|
38
|
+
*/
|
|
31
39
|
refresh(): Promise<void>;
|
|
32
40
|
back(): void;
|
|
33
41
|
forward(): void;
|
|
@@ -35,8 +43,22 @@ export type Router = {
|
|
|
35
43
|
export type SubmitBody = EncodedForm;
|
|
36
44
|
/** Called with every server action response, e.g. by the query shell to apply query updates. */
|
|
37
45
|
export type ActionListener = (response: ActionResponse) => void;
|
|
38
|
-
|
|
39
|
-
|
|
46
|
+
/**
|
|
47
|
+
* What `<Link>` and `<Form>` also pass: what had the focus before the app's own `onClick` or `onSubmit` ran. Those run
|
|
48
|
+
* as the navigation starts, so where they move the focus, such as back to the button of a menu that closes, is where
|
|
49
|
+
* the navigation put it.
|
|
50
|
+
*/
|
|
51
|
+
type FocusAtStart = {
|
|
52
|
+
focusedAtStart?: Element | null;
|
|
53
|
+
};
|
|
54
|
+
export type InternalRouter = Omit<Router, 'push' | 'replace'> & {
|
|
55
|
+
push(href: Href, options?: {
|
|
56
|
+
scroll?: boolean;
|
|
57
|
+
} & FocusAtStart): Promise<void>;
|
|
58
|
+
replace(href: Href, options?: {
|
|
59
|
+
scroll?: boolean;
|
|
60
|
+
} & FocusAtStart): Promise<void>;
|
|
61
|
+
submit(action: URL, body: SubmitBody, options?: FocusAtStart): Promise<void>;
|
|
40
62
|
onActionResponse(listener: ActionListener): () => void;
|
|
41
63
|
/**
|
|
42
64
|
* Called as soon as a server action call or form post says it set or deleted a cookie (signed someone in or out),
|
|
@@ -82,7 +104,7 @@ export declare function useInternalRouter(): InternalRouter;
|
|
|
82
104
|
export declare function useRouter(): Router;
|
|
83
105
|
/** The path without the base path, as the app's routes see it (like Next.js's `usePathname` with `basePath`). */
|
|
84
106
|
export declare function usePathname(): string;
|
|
85
|
-
/** An href for a plain `<a
|
|
107
|
+
/** An href for a plain `<a>`, another attribute or a `fetch`, under the base path: what `<Link>` does for its own. */
|
|
86
108
|
export declare function useHref(href: string): string;
|
|
87
109
|
/** `UsePathBase`'s base path for this page, or '' when the app is mounted at the root. */
|
|
88
110
|
export declare function useBasePath(): string;
|
|
@@ -115,4 +137,5 @@ export declare function useSelectedLayoutSegment(): string | null;
|
|
|
115
137
|
export declare function usePagePayload(): PagePayload;
|
|
116
138
|
/** The router's state: `loading` during a navigation, `submitting` during a form post, else `idle` (with `error` after a post that produced no page). */
|
|
117
139
|
export declare function useNavigation(): NavigationState;
|
|
140
|
+
export {};
|
|
118
141
|
//# sourceMappingURL=router.d.ts.map
|
package/dist/router.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,SAAS,
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.tsx"],"names":[],"mappings":"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"}
|
package/dist/router.js
CHANGED
|
@@ -1,19 +1,33 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Suspense, createContext, startTransition, use, useContext, useEffect, useLayoutEffect, useMemo, useState, } from 'react';
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Suspense, createContext, startTransition, use, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
|
|
3
3
|
import { actionRequest, expectActionClient, fetchAction, setActionClient } from './actions.js';
|
|
4
4
|
import { deferredRegistry, revivePayload } from './deferred.js';
|
|
5
5
|
import { MissingModuleError } from './modules.js';
|
|
6
|
+
import { moveFocusAfterNavigation } from './navigation-focus.js';
|
|
6
7
|
import { COOKIES_HEADER, DATA_HEADER, SEGMENTS_HEADER, SERVER_TIME_HEADER, parseAntiforgery, parseNavigationPayload, } from './payload.js';
|
|
7
|
-
import {
|
|
8
|
+
import { RouteAnnouncer } from './route-announcer.js';
|
|
9
|
+
import { ErrorBoundary, LayoutDepthContext, RouteTree, asError } from './route-tree.js';
|
|
8
10
|
import { parseLenient, parseWith } from './schema.js';
|
|
9
11
|
function isLoaded(result) {
|
|
10
12
|
return 'payload' in result;
|
|
11
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* What remounts the page and its templates when it changes: the page's path, or for an intercepted page the key of the
|
|
16
|
+
* page under it, which stays on screen. Two views with one key show the same page.
|
|
17
|
+
*/
|
|
18
|
+
const pageKeyOf = (loaded) => loaded.pageKey ?? loaded.url.pathname;
|
|
12
19
|
const IDLE = { state: 'idle' };
|
|
13
20
|
const ANTIFORGERY_URL = '/_netjes/antiforgery';
|
|
14
21
|
const NONE = { kind: 'none' };
|
|
15
22
|
const TOP = { kind: 'top' };
|
|
16
23
|
const STATE_KEY = '__netjes';
|
|
24
|
+
/**
|
|
25
|
+
* On `<html>` while the client router runs in the document, and the public sign that it does: end-to-end tests wait
|
|
26
|
+
* for it before they click. (The history entry's key is no such sign: the entry keeps it when the browser loads the
|
|
27
|
+
* document again.) The effect that sets it must stay after the one that registers the router as the action client,
|
|
28
|
+
* so that once it shows, the router has keyed the entry, listens for back and forward, and runs server action calls.
|
|
29
|
+
*/
|
|
30
|
+
const HYDRATED_ATTRIBUTE = 'data-netjes-hydrated';
|
|
17
31
|
const PREFETCH_TTL_MS = 30_000;
|
|
18
32
|
const MAX_REDIRECTS = 10;
|
|
19
33
|
const MAX_REMEMBERED_ENTRIES = 50;
|
|
@@ -247,7 +261,8 @@ class Navigator {
|
|
|
247
261
|
this.channel?.close();
|
|
248
262
|
this.channel = null;
|
|
249
263
|
}
|
|
250
|
-
|
|
264
|
+
/** `focusedAtStart`: what had the focus as the navigation started, by default what has it as this is called. */
|
|
265
|
+
async navigate(href, history, scroll, focusedAtStart = document.activeElement) {
|
|
251
266
|
const url = new URL(withBasePath(href, this.deps.basePath), window.location.href);
|
|
252
267
|
if (!isWebUrl(url)) {
|
|
253
268
|
console.error(`netjes: refusing to navigate to ${url.protocol} URL`);
|
|
@@ -264,7 +279,7 @@ class Navigator {
|
|
|
264
279
|
const key = createKey();
|
|
265
280
|
window.history[history === 'push' ? 'pushState' : 'replaceState']({ [STATE_KEY]: key }, '', url.href);
|
|
266
281
|
this.setCurrent(key, { ...this.current, url });
|
|
267
|
-
this.deps.commit(this.current, scroll ? { kind: 'hash', hash: url.hash } : NONE);
|
|
282
|
+
this.deps.commit(this.current, scroll ? { kind: 'hash', hash: url.hash } : NONE, focusedAtStart);
|
|
268
283
|
return;
|
|
269
284
|
}
|
|
270
285
|
await this.go(url, { method: 'GET' }, {
|
|
@@ -273,10 +288,11 @@ class Navigator {
|
|
|
273
288
|
pending: 'loading',
|
|
274
289
|
usePrefetch: true,
|
|
275
290
|
reuseSegments: true,
|
|
291
|
+
focusedAtStart,
|
|
276
292
|
});
|
|
277
293
|
}
|
|
278
|
-
submit(action, body) {
|
|
279
|
-
return this.go(action, { method: 'POST', ...body }, { history: 'auto', scroll: 'auto', pending: 'submitting', usePrefetch: false, reuseSegments: false });
|
|
294
|
+
submit(action, body, focusedAtStart = document.activeElement) {
|
|
295
|
+
return this.go(action, { method: 'POST', ...body }, { history: 'auto', scroll: 'auto', pending: 'submitting', usePrefetch: false, reuseSegments: false, focusedAtStart });
|
|
280
296
|
}
|
|
281
297
|
/**
|
|
282
298
|
* Loads the page on screen again, in place: same history entry, fragment and scroll position. `keepPrefetched` after
|
|
@@ -420,12 +436,16 @@ class Navigator {
|
|
|
420
436
|
if (known) {
|
|
421
437
|
this.active?.controller.abort();
|
|
422
438
|
this.setCurrent(key, { ...known, url });
|
|
423
|
-
this.deps.commit(this.current, scroll);
|
|
439
|
+
this.deps.commit(this.current, scroll, document.activeElement);
|
|
424
440
|
return;
|
|
425
441
|
}
|
|
426
442
|
void this.go(url, { method: 'GET' }, { history: 'none', scroll, pending: 'loading', key, usePrefetch: false, reuseSegments: true });
|
|
427
443
|
}
|
|
428
444
|
async go(url, request, options) {
|
|
445
|
+
// Read before anything this navigation starts can move it: inside the click (or the keypress) that started it,
|
|
446
|
+
// before React has handled that event's own state updates, such as a menu that closes. navigate() and submit()
|
|
447
|
+
// pass what they read, or what a link or a form read before the app's own handler ran.
|
|
448
|
+
const focusedAtStart = options.focusedAtStart === undefined ? document.activeElement : options.focusedAtStart;
|
|
429
449
|
if (url.origin !== window.location.origin) {
|
|
430
450
|
// navigate() and Form hand other origins to the browser, and every page URL the router keeps is on this one.
|
|
431
451
|
// Refuse one that gets here all the same: a failed load would send the browser to it.
|
|
@@ -505,7 +525,7 @@ class Navigator {
|
|
|
505
525
|
this.forgetToken();
|
|
506
526
|
committed = true;
|
|
507
527
|
const scroll = shown.base ? NONE : options.scroll === 'auto' ? (shown.url.pathname === previous.pathname ? NONE : TOP) : options.scroll;
|
|
508
|
-
this.deps.commit(shown, scroll);
|
|
528
|
+
this.deps.commit(shown, scroll, focusedAtStart);
|
|
509
529
|
}
|
|
510
530
|
catch (error) {
|
|
511
531
|
if (controller.signal.aborted)
|
|
@@ -678,23 +698,24 @@ function elementForFragment(fragment) {
|
|
|
678
698
|
}
|
|
679
699
|
return document.getElementById(decoded) ?? document.getElementById(fragment);
|
|
680
700
|
}
|
|
701
|
+
/** Scrolls as the intent says, and returns the element a fragment names, which then takes the focus. */
|
|
681
702
|
function applyScroll(intent) {
|
|
682
703
|
switch (intent.kind) {
|
|
683
704
|
case 'none':
|
|
684
|
-
return;
|
|
705
|
+
return null;
|
|
685
706
|
case 'top':
|
|
686
707
|
window.scrollTo(0, 0);
|
|
687
|
-
return;
|
|
708
|
+
return null;
|
|
688
709
|
case 'restore':
|
|
689
710
|
window.scrollTo(intent.x, intent.y);
|
|
690
|
-
return;
|
|
711
|
+
return null;
|
|
691
712
|
case 'hash': {
|
|
692
713
|
const target = elementForFragment(intent.hash.slice(1));
|
|
693
714
|
if (target)
|
|
694
715
|
target.scrollIntoView();
|
|
695
716
|
else
|
|
696
717
|
window.scrollTo(0, 0);
|
|
697
|
-
return;
|
|
718
|
+
return target;
|
|
698
719
|
}
|
|
699
720
|
default: {
|
|
700
721
|
const exhaustive = intent;
|
|
@@ -737,6 +758,7 @@ export function RouterProvider({ initial, initialModules, loader, errorPage, har
|
|
|
737
758
|
},
|
|
738
759
|
id: 0,
|
|
739
760
|
scroll: NONE,
|
|
761
|
+
focusedAtStart: null,
|
|
740
762
|
}));
|
|
741
763
|
const [navigation, setNavigation] = useState(IDLE);
|
|
742
764
|
const [navigator] = useState(() => {
|
|
@@ -751,8 +773,8 @@ export function RouterProvider({ initial, initialModules, loader, errorPage, har
|
|
|
751
773
|
showDocument,
|
|
752
774
|
download,
|
|
753
775
|
setPending: setNavigation,
|
|
754
|
-
commit: (loaded, scroll) => startTransition(() => {
|
|
755
|
-
setView((previous) => ({ loaded, id: previous.id + 1, scroll }));
|
|
776
|
+
commit: (loaded, scroll, focusedAtStart) => startTransition(() => {
|
|
777
|
+
setView((previous) => ({ loaded, id: previous.id + 1, scroll, focusedAtStart }));
|
|
756
778
|
setNavigation(IDLE);
|
|
757
779
|
}),
|
|
758
780
|
}, view.loaded);
|
|
@@ -811,18 +833,36 @@ export function RouterProvider({ initial, initialModules, loader, errorPage, har
|
|
|
811
833
|
return response;
|
|
812
834
|
},
|
|
813
835
|
}), [navigator, actionListeners]);
|
|
836
|
+
// Directly after the registration above, and in a passive effect, never a layout one: React runs a component's
|
|
837
|
+
// effects in order, a commit's layout effects before any passive one, and the mark must not show while server
|
|
838
|
+
// action calls still wait for the router.
|
|
839
|
+
useEffect(() => {
|
|
840
|
+
const html = document.documentElement;
|
|
841
|
+
html.setAttribute(HYDRATED_ATTRIBUTE, '');
|
|
842
|
+
return () => html.removeAttribute(HYDRATED_ATTRIBUTE);
|
|
843
|
+
}, []);
|
|
844
|
+
// The key of the page on screen before this commit.
|
|
845
|
+
const committedPageKey = useRef(pageKeyOf(view.loaded));
|
|
846
|
+
// In the commit that shows the page, before the browser paints it, and after the layout effects of the page and its
|
|
847
|
+
// layouts, which have focused an autoFocus element by then. Every focus call keeps the scroll position applyScroll
|
|
848
|
+
// sets, the one Back restores included.
|
|
814
849
|
useLayoutEffect(() => {
|
|
815
|
-
if (view.id
|
|
816
|
-
|
|
850
|
+
if (view.id === 0)
|
|
851
|
+
return;
|
|
852
|
+
const target = applyScroll(view.scroll);
|
|
853
|
+
const key = pageKeyOf(view.loaded);
|
|
854
|
+
const replaced = key !== committedPageKey.current;
|
|
855
|
+
committedPageKey.current = key;
|
|
856
|
+
moveFocusAfterNavigation({ target, replaced, keep: view.scroll.kind === 'none', focusedAtStart: view.focusedAtStart });
|
|
817
857
|
}, [view]);
|
|
818
858
|
const router = useMemo(() => ({
|
|
819
|
-
push: (href, options) => navigator.navigate(href, 'push', options?.scroll ?? true),
|
|
820
|
-
replace: (href, options) => navigator.navigate(href, 'replace', options?.scroll ?? true),
|
|
859
|
+
push: (href, options) => navigator.navigate(href, 'push', options?.scroll ?? true, options?.focusedAtStart),
|
|
860
|
+
replace: (href, options) => navigator.navigate(href, 'replace', options?.scroll ?? true, options?.focusedAtStart),
|
|
821
861
|
prefetch: (href) => navigator.prefetch(href),
|
|
822
862
|
refresh: () => navigator.refresh(),
|
|
823
863
|
back: () => window.history.back(),
|
|
824
864
|
forward: () => window.history.forward(),
|
|
825
|
-
submit: (action, body) => navigator.submit(action, body),
|
|
865
|
+
submit: (action, body, options) => navigator.submit(action, body, options?.focusedAtStart),
|
|
826
866
|
onActionResponse: (listener) => {
|
|
827
867
|
actionListeners.add(listener);
|
|
828
868
|
return () => actionListeners.delete(listener);
|
|
@@ -831,11 +871,16 @@ export function RouterProvider({ initial, initialModules, loader, errorPage, har
|
|
|
831
871
|
}), [navigator, actionListeners]);
|
|
832
872
|
const { payload, modules, url } = view.loaded;
|
|
833
873
|
const viewValue = useMemo(() => ({ payload, url }), [payload, url]);
|
|
834
|
-
return (_jsx(RouterContext, { value: router, children: _jsx(ViewContext, { value: viewValue, children:
|
|
874
|
+
return (_jsx(RouterContext, { value: router, children: _jsx(ViewContext, { value: viewValue, children: _jsxs(NavigationContext, { value: navigation, children: [_jsx(Shell, { children: _jsx(ErrorBoundary, { resetKey: view.id, fallback: (error) => _jsx(ErrorFallback, { error: error, errorPage: errorPage, loader: loader, payload: payload }), children: _jsx(RouteTree, { modules: modules, segments: payload.segments, props: payload.props, params: payload.params, status: payload.status, pageKind: payload.pageKind, pageKey: pageKeyOf(view.loaded), retry: () => void router.refresh(), metadata: payload.metadata }) }) }), _jsx(RouteAnnouncer, { location: url.pathname + url.search, path: withoutBasePath(url.pathname, payload.basePath ?? '') })] }) }) }));
|
|
835
875
|
}
|
|
836
876
|
const errorPageModules = new WeakMap();
|
|
877
|
+
/**
|
|
878
|
+
* What the router shows for an error no segment's boundary caught: the app's root error page, handed the error as it
|
|
879
|
+
* was thrown (its class, its stack, a server action's status), or the built-in page, which says 500.
|
|
880
|
+
*/
|
|
837
881
|
function ErrorFallback({ error, errorPage, loader, payload }) {
|
|
838
|
-
const
|
|
882
|
+
const caught = asError(error);
|
|
883
|
+
const props = { status: 500, message: caught.message };
|
|
839
884
|
const builtIn = _jsx(RouteTree, { modules: { page: null, segments: [] }, props: props, params: payload.params, status: 500, pageKey: "error" });
|
|
840
885
|
if (!errorPage)
|
|
841
886
|
return builtIn;
|
|
@@ -844,10 +889,10 @@ function ErrorFallback({ error, errorPage, loader, payload }) {
|
|
|
844
889
|
modules = loader.loadRoute(errorPage);
|
|
845
890
|
errorPageModules.set(loader, modules);
|
|
846
891
|
}
|
|
847
|
-
return (_jsx(ErrorBoundary, { resetKey: 0, fallback: () => builtIn, children: _jsx(Suspense, { fallback: null, children: _jsx(LoadedErrorPage, { modules: modules, props: props, payload: payload }) }) }));
|
|
892
|
+
return (_jsx(ErrorBoundary, { resetKey: 0, fallback: () => builtIn, children: _jsx(Suspense, { fallback: null, children: _jsx(LoadedErrorPage, { modules: modules, props: props, error: caught, payload: payload }) }) }));
|
|
848
893
|
}
|
|
849
|
-
function LoadedErrorPage({ modules, props, payload }) {
|
|
850
|
-
return _jsx(RouteTree, { modules: use(modules), props: props, params: payload.params, status: 500, pageKind: "error", pageKey: "error" });
|
|
894
|
+
function LoadedErrorPage({ modules, props, error, payload, }) {
|
|
895
|
+
return _jsx(RouteTree, { modules: use(modules), props: props, params: payload.params, status: 500, pageKind: "error", pageKey: "error", error: error });
|
|
851
896
|
}
|
|
852
897
|
/** The router's contexts without the router: for the test harness (netjes/testing), which fakes navigation. */
|
|
853
898
|
export function RouterContexts({ router, payload, url, navigation, children, }) {
|
|
@@ -875,7 +920,7 @@ export function usePathname() {
|
|
|
875
920
|
const { url, payload } = useView();
|
|
876
921
|
return withoutBasePath(url.pathname, payload.basePath ?? '');
|
|
877
922
|
}
|
|
878
|
-
/** An href for a plain `<a
|
|
923
|
+
/** An href for a plain `<a>`, another attribute or a `fetch`, under the base path: what `<Link>` does for its own. */
|
|
879
924
|
export function useHref(href) {
|
|
880
925
|
return withBasePath(href, useBasePath());
|
|
881
926
|
}
|