Skip to content

Commit

Permalink
WIP9
Browse files Browse the repository at this point in the history
  • Loading branch information
smikhalevski committed Nov 22, 2024
1 parent f98dc94 commit 342ea48
Show file tree
Hide file tree
Showing 52 changed files with 758 additions and 3,127 deletions.
124 changes: 0 additions & 124 deletions src/main/InternalRouter.tsx

This file was deleted.

110 changes: 87 additions & 23 deletions src/main/Outlet.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,94 @@
import React, { ReactNode, useContext } from 'react';
import { ChildSlotControllerContext, Slot } from './Slot';

/**
* Props of the {@link Outlet} component.
*
* @group Components
*/
export interface OutletProps {
import React, { Component, createContext, ReactNode, Suspense } from 'react';
import { Presenter } from './Presenter';

export const PresenterContext = createContext<Presenter | null>(null);

export const ChildPresenterContext = createContext<Presenter | null>(null);

interface OutletState {
/**
* An error captured by an error boundary.
*/
error: unknown;

/**
* A content that is rendered if there's no route to render.
* `true` if an error boundary was triggered.
*/
children?: ReactNode;
hasError: boolean;
}

export class Outlet extends Component<{}, OutletState> {
static contextType = ChildPresenterContext;

declare context: Presenter | null;

// static getDerivedStateFromError(error: unknown): Partial<SlotState> | null {
// return { error, hasError: true };
// }

// static getDerivedStateFromProps(nextProps: Readonly<SlotProps>, prevState: SlotState): Partial<SlotState> | null {
// if (prevState.hasError) {
// nextProps.controller.renderedController.onCatch(prevState.error);
//
// return { error: undefined, hasError: false };
// }
// return null;
// }

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

const { renderedPresenter } = this.context;

const children = (
<InternalOutlet
canSuspend={true}
presenter={renderedPresenter}
/>
);

if (renderedPresenter.loadingComponent === undefined) {
return children;
}

return (
<Suspense
fallback={
<InternalOutlet
canSuspend={false}
presenter={renderedPresenter}
/>
}
>
{children}
</Suspense>
);
}
}

interface InternalOutletProps {
canSuspend: boolean;
presenter: Presenter;
}

/**
* Renders a route provided by an enclosing {@link Router}.
*
* @group Components
*/
export function Outlet(props: OutletProps): ReactNode {
const controller = useContext(ChildSlotControllerContext);
function InternalOutlet(props: InternalOutletProps): ReactNode {
const { canSuspend, presenter } = props;

const Component = canSuspend ? presenter.getComponentOrSuspend() : presenter.loadingComponent;

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

return controller === undefined ? props.children : <Slot controller={controller} />;
return (
<PresenterContext.Provider value={presenter}>
<ChildPresenterContext.Provider value={presenter.childPresenter}>
<Component />
</ChildPresenterContext.Provider>
</PresenterContext.Provider>
);
}

/**
* @internal
*/
Outlet.displayName = 'Outlet';
InternalOutlet.displayName = 'InternalOutlet';
22 changes: 17 additions & 5 deletions src/main/PathnameTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ export class PathnameTemplate {

/**
* Matches a pathname against a pathname pattern.
*
* @param pathname A pathname to match.
* @returns A matching result, or `null` if {@link pathname} doesn't match the template.
*/
match(pathname: string): PathnameMatch | null {
const { _segments, _flags } = this;
Expand Down Expand Up @@ -111,7 +114,10 @@ export class PathnameTemplate {
}

/**
* Creates a pathname from a template by substituting params, beginning with a "/".
* Creates a pathname (starts with a "/") from a template by substituting params.
*
* @param params Params to substitute into a template.
* @returns A pathname string.
*/
toPathname(params?: Dict | void): string {
const { _segments, _flags } = this;
Expand All @@ -134,8 +140,12 @@ export class PathnameTemplate {
continue;
}

if (typeof value !== 'string') {
throw new Error('Param must be a string: ' + segment);
if (Number.isFinite(value)) {
value += '';
}

if (typeof value !== 'string' || value === '') {
throw new Error('Param must be a non-empty string or a number: ' + segment);
}

pathname +=
Expand All @@ -159,7 +169,7 @@ const STAGE_OPTIONAL = 4;
/**
* A result of a pathname pattern parsing.
*/
interface Template {
export interface Template {
/**
* A non-empty array of segments and param names extracted from a pathname pattern.
*/
Expand Down Expand Up @@ -275,7 +285,9 @@ export function parsePattern(pattern: string): Template {
/**
* Creates a {@link !RegExp} that matches a pathname template.
*/
export function createPatternRegExp({ segments, flags }: Template, isCaseSensitive = false): RegExp {
export function createPatternRegExp(template: Template, isCaseSensitive = false): RegExp {
const { segments, flags } = template;

let pattern = '^';

for (let i = 0, segment, flag, segmentPattern; i < segments.length; ++i) {
Expand Down
Loading

0 comments on commit 342ea48

Please sign in to comment.