Skip to content

Commit

Permalink
WIP7
Browse files Browse the repository at this point in the history
  • Loading branch information
smikhalevski committed Nov 21, 2024
1 parent 74b5a92 commit 721be75
Show file tree
Hide file tree
Showing 9 changed files with 1,243 additions and 711 deletions.
12 changes: 5 additions & 7 deletions jest.config.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
{
"preset": "ts-jest",
"testMatch": [
"<rootDir>/src/**/*.test.{tsx,ts}"
],
"modulePathIgnorePatterns": [
"<rootDir>/lib"
],
"transform": {
"^.+\\.tsx?$": "@swc/jest"
},
"testMatch": ["<rootDir>/src/test/**/*.test.ts"],
"modulePathIgnorePatterns": ["<rootDir>/lib"],
"detectOpenHandles": true,
"forceExit": true
}
1,763 changes: 1,117 additions & 646 deletions package-lock.json

Large diffs are not rendered by default.

25 changes: 13 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,23 +37,24 @@
},
"homepage": "https://github.com/smikhalevski/react-corsair#readme",
"devDependencies": {
"@rollup/plugin-typescript": "^11.1.6",
"@testing-library/jest-dom": "^6.4.8",
"@testing-library/react": "^16.0.0",
"@types/jest": "^29.5.12",
"@rollup/plugin-typescript": "^12.1.1",
"@swc/core": "^1.9.2",
"@swc/jest": "^0.2.37",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.0.1",
"@types/jest": "^29.5.14",
"@types/jsdom": "^21.1.7",
"@types/react": "^18.3.3",
"@types/react": "^18.3.12",
"jest": "^29.7.0",
"jsdom": "^24.1.1",
"jsdom": "^25.0.1",
"prettier": "^3.3.3",
"rimraf": "^6.0.1",
"rollup": "^4.20.0",
"ts-jest": "^29.2.4",
"tslib": "^2.6.3",
"typedoc": "^0.26.5",
"rollup": "^4.27.3",
"tslib": "^2.8.1",
"typedoc": "^0.26.11",
"typedoc-custom-css": "github:smikhalevski/typedoc-custom-css#master",
"typedoc-plugin-mdn-links": "^3.2.8",
"typescript": "^5.5.4"
"typedoc-plugin-mdn-links": "^3.3.8",
"typescript": "^5.6.3"
},
"peerDependencies": {
"react": ">=18.0.0"
Expand Down
61 changes: 31 additions & 30 deletions src/main/____NEW/Outlet.tsx
Original file line number Diff line number Diff line change
@@ -1,72 +1,73 @@
import React, { Component, createContext, createElement, ReactNode, Suspense } from 'react';
import { useRouter } from './RouterProvider';
import React, { Component, createContext, ReactNode, Suspense } from 'react';
import { createRoute } from './__createRoute';
import { LoaderOptions } from './__types';
import { View } from './View';

export const ViewContext = createContext<View | undefined>(undefined);
export const ViewContext = createContext<View | null>(null);

export const OutletViewContext = createContext<View | undefined>(undefined);
export const NestedViewContext = createContext<View | null>(null);

export interface OutletProps {
children?: ReactNode;
}

export class Outlet extends Component<OutletProps, any> {
static contextType = OutletViewContext;
static contextType = NestedViewContext;

declare context: View | undefined;
declare context: View | null;

render(): ReactNode {
if (this.context === undefined) {
if (this.context === null) {
return this.props.children;
}

const { renderedView } = this.context;

const children = (
<InternalOutlet
canSuspend={true}
view={renderedView}
/>
);

if (!renderedView.hasSuspendBoundary()) {
return children;
}

return (
<Suspense
fallback={
<InternalOutlet
isSuspendable={false}
view={this.context.renderedController}
canSuspend={false}
view={renderedView}
/>
}
>
<InternalOutlet
isSuspendable={true}
view={this.context.renderedController}
/>
{children}
</Suspense>
);
}
}

interface InternalOutletProps {
isSuspendable: boolean;
canSuspend: boolean;
view: View;
}

function InternalOutlet(props: InternalOutletProps): ReactNode {
const { isSuspendable, view } = props;

const router = useRouter();
const { canSuspend, view } = props;

let component;

if (isSuspendable) {
view.onSuspend();
component = view.component;
} else {
component = view.fallbackComponent;
}
const Component = canSuspend ? view.getComponentOrSuspend() : view.getFallbackComponent();

if (component === undefined) {
if (Component === undefined) {
return null;
}

return (
<ViewContext.Provider value={view}>
<OutletViewContext.Provider value={view.childView}>
{isSuspendable && router.renderHydrationScript()}
{createElement(component)}
</OutletViewContext.Provider>
<NestedViewContext.Provider value={view.nestedView}>
<Component />
</NestedViewContext.Provider>
</ViewContext.Provider>
);
}
Expand Down
22 changes: 19 additions & 3 deletions src/main/____NEW/Router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class Router<Context = any> {
notFoundComponent: ComponentType | undefined;

/**
* A view rendered in the router {@link Outlet}.
* A root view rendered by the router.
*/
rootView: View;

Expand All @@ -57,15 +57,31 @@ export class Router<Context = any> {
this.errorComponent = options.errorComponent;
this.loadingComponent = options.loadingComponent;
this.notFoundComponent = options.notFoundComponent;
this.rootView = new View(this);
this.rootView = new View(this, undefined);
}

/**
* Looks up a route in {@link routes} that matches a location, and returns an array of matches for a route and its
* ancestors.
*
* @param to A location or a route to match.
*/
match(to: To): RouteMatch[] {
const location = toLocation(to);
return matchRoutes(location.pathname, location.searchParams, this.routes);
}

navigate(to: To): void {}
navigate(to: To): void {
const routeMatch = this.match(to)[0];

new View(this, routeMatch).routeState = loadRoute(routeMatch.route, {
params: routeMatch.params,
context: this.context,
isPreload: false,
});

//rerender()
}

/**
* Prefetches components and data of routes matched by a location.
Expand Down
4 changes: 2 additions & 2 deletions src/main/____NEW/RouterProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { createContext, ReactElement, ReactNode, useContext } from 'react';
import { OutletViewContext, Outlet } from './Outlet';
import { NestedViewContext, Outlet } from './Outlet';
import { Router } from './Router';

const RouterContext = createContext<Router | null>(null);
Expand All @@ -16,7 +16,7 @@ export function RouterProvider(props: RouterProviderProps): ReactElement {

return (
<RouterContext.Provider value={router}>
<OutletViewContext.Provider value={router.rootView}>{children}</OutletViewContext.Provider>
<NestedViewContext.Provider value={router.rootView}>{children}</NestedViewContext.Provider>
</RouterContext.Provider>
);
}
Expand Down
31 changes: 26 additions & 5 deletions src/main/____NEW/View.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,37 @@
import { ComponentType } from 'react';
import { RouteState } from './__loadRoute';
import { RouteMatch } from './__matchRoutes';
import { Router } from './Router';

export class View {
childView: View | undefined;
parentView: View | null = null;
nestedView: View | null = null;

/**
* State of a route rendered by the view, or `undefined` is there's no route, or route wasn't loaded yet.
*/
routeState: Promise<RouteState> | RouteState | undefined = undefined;

renderedView: View = this;

constructor(
readonly router: Router,
readonly routeMatch?: RouteMatch
router: Router,
readonly routeMatch: RouteMatch | undefined
) {}

setPayload(routePayload: RouteState): void {}
hasErrorBoundary(): boolean {
return true;
}

hasSuspendBoundary(): boolean {
return true;
}

getComponentOrSuspend(): ComponentType | undefined {
return;
}

freeze(): void {}
getFallbackComponent(): ComponentType | undefined {
return;
}
}
32 changes: 29 additions & 3 deletions src/main/____NEW/__Route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { ComponentType } from 'react';
import { PathnameTemplate } from './__PathnameTemplate';
import { Outlet } from './Outlet';
import {
Dict,
LoaderOptions,
Expand All @@ -11,12 +10,27 @@ import {
RenderingDisposition,
RouteOptions,
} from './__types';
import { Outlet } from './Outlet';

type Prettify<T> = { [K in keyof T]: T[K] } & {};

type PartialVoid<T> = Partial<T> extends T ? T | void : T;

type InferParams<R extends Route | null> = R extends Route<any, infer Params> ? Params : object | void;
declare const PARAMS: unique symbol;
declare const CONTEXT: unique symbol;

type PARAMS = typeof PARAMS;
type CONTEXT = typeof CONTEXT;

/**
* Infers cumulative route params.
*/
export type InferParams<R extends Route> = R[PARAMS];

/**
* Infers required router context.
*/
export type InferContext<R extends Route> = R[CONTEXT];

/**
* A route that can be rendered by a router.
Expand All @@ -35,6 +49,18 @@ export class Route<
Data = any,
Context = any,
> {
/**
* The type of cumulative route params.
*
* @internal
*/
declare readonly [PARAMS]: PartialVoid<ParentRoute extends Route ? Prettify<ParentRoute[PARAMS] & Params> : Params>;

/**
* The type of required router context.
*/
declare readonly [CONTEXT]: Context;

/**
* A parent route or `null` if there is no parent.
*/
Expand Down Expand Up @@ -162,7 +188,7 @@ export class Route<
* @param params Route params.
* @param options Location options.
*/
getLocation(params: PartialVoid<Prettify<InferParams<ParentRoute> & Params>>, options?: LocationOptions): Location {
getLocation(params: InferParams<this>, options?: LocationOptions): Location {
let pathname = '';
let searchParams: Dict = {};
let hasLooseParams = false;
Expand Down
4 changes: 1 addition & 3 deletions src/main/____NEW/__createRoute.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { ComponentType } from 'react';
import { Route } from './__Route';
import { InferContext, Route } from './__Route';
import { RouteOptions } from './__types';

type InferContext<R extends Route> = R extends Route<any, any, any, infer Context> ? Context : any;

/**
* Creates a route that is rendered in an {@link Outlet} of a {@link Router}.
*
Expand Down

0 comments on commit 721be75

Please sign in to comment.