Initial commit
This commit is contained in:
940
node_modules/wouter/README.md
generated
vendored
Normal file
940
node_modules/wouter/README.md
generated
vendored
Normal file
@ -0,0 +1,940 @@
|
||||
<div align="center">
|
||||
<img src="assets/logo.svg" width="80" alt="Wouter — a super-tiny React router (logo by Katya Simacheva)" />
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<div align="center">
|
||||
<a href="https://npmjs.org/package/wouter"><img alt="npm" src="https://img.shields.io/npm/v/wouter.svg?color=black&labelColor=888" /></a>
|
||||
<a href="https://travis-ci.org/molefrog/wouter"><img alt="CI" src="https://img.shields.io/github/actions/workflow/status/molefrog/wouter/size.yml?color=black&labelColor=888&label=2.5KB+limit" /></a>
|
||||
<a href="https://codecov.io/gh/molefrog/wouter"><img alt="Coverage" src="https://img.shields.io/codecov/c/github/molefrog/wouter.svg?color=black&labelColor=888" /></a>
|
||||
<a href="https://www.npmjs.com/package/wouter"><img alt="Coverage" src="https://img.shields.io/npm/dm/wouter.svg?color=black&labelColor=888" /></a>
|
||||
<a href="https://pr.new/molefrog/wouter"><img alt="Edit in StackBlitz IDE" src="https://img.shields.io/badge/StackBlitz-New%20PR-black?labelColor=888" /></a>
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<b>wouter</b> is a tiny router for modern React and Preact apps that relies on Hooks. <br />
|
||||
A router you wanted so bad in your project!<br>
|
||||
</div>
|
||||
|
||||
## Features
|
||||
|
||||
> ⚠️ These docs are for wouter v3 only. Please find the documentation for [wouter@2.12.0 here](https://github.com/molefrog/wouter/tree/main)
|
||||
|
||||
<img src="assets/wouter.svg" align="right" width="250" alt="by Katya Simacheva" />
|
||||
|
||||
- Minimum dependencies, only **2.1 KB** gzipped vs 18.7KB
|
||||
[React Router](https://github.com/ReactTraining/react-router).
|
||||
- Supports both **React** and **[Preact](https://preactjs.com/)**! Read
|
||||
_["Preact support" section](#preact-support)_ for more details.
|
||||
- No top-level `<Router />` component, it is **fully optional**.
|
||||
- Mimics [React Router](https://github.com/ReactTraining/react-router)'s best practices by providing
|
||||
familiar **[`Route`](#route-pathpattern-)**, **[`Link`](#link-hrefpath-)**,
|
||||
**[`Switch`](#switch-)** and **[`Redirect`](#redirect-topath-)** components.
|
||||
- Has hook-based API for more granular control over routing (like animations):
|
||||
**[`useLocation`](#uselocation-working-with-the-history)**,
|
||||
**[`useRoute`](#useroute-route-matching-and-parameters)** and
|
||||
**[`useRouter`](#userouter-accessing-the-router-object)**.
|
||||
|
||||
## developers :sparkling_heart: wouter
|
||||
|
||||
> ... I love Wouter. It’s tiny, fully embraces hooks, and has an intuitive and barebones API. I can
|
||||
> accomplish everything I could with react-router with Wouter, and it just feels **more minimalist
|
||||
> while not being inconvenient.**
|
||||
>
|
||||
> [**Matt Miller**, _An exhaustive React ecosystem for 2020_](https://medium.com/@mmiller42/an-exhaustive-react-guide-for-2020-7859f0bddc56)
|
||||
|
||||
Wouter provides a simple API that many developers and library authors appreciate. Some notable
|
||||
projects that use wouter: **[Ultra](https://ultrajs.dev/)**,
|
||||
**[React-three-fiber](https://github.com/react-spring/react-three-fiber)**,
|
||||
**[Sunmao UI](https://sunmao-ui.com/)**, **[Million](https://millionjs.org/)** and many more.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Getting Started](#getting-started)
|
||||
- [Browser Support](#browser-support)
|
||||
- [Wouter API](#wouter-api)
|
||||
- [The list of methods available](#the-list-of-methods-available)
|
||||
- [Hooks API](#hooks-api)
|
||||
- [`useRoute`: route matching and parameters](#useroute-route-matching-and-parameters)
|
||||
- [`useLocation`: working with the history](#uselocation-working-with-the-history)
|
||||
- [Additional navigation parameters](#additional-navigation-parameters)
|
||||
- [Customizing the location hook](#customizing-the-location-hook)
|
||||
- [`useParams`: extracting matched parameters](#useparams-extracting-matched-parameters)
|
||||
- [`useSearch`: query strings](#usesearch-query-strings)
|
||||
- [`useRouter`: accessing the router object](#userouter-accessing-the-router-object)
|
||||
- [Component API](#component-api)
|
||||
|
||||
- [`<Route path={pattern} />`](#route-pathpattern-)
|
||||
- [Route nesting](#route-nesting)
|
||||
- [`<Link href={path} />`](#link-hrefpath-)
|
||||
- [`<Switch />`](#switch-)
|
||||
- [`<Redirect to={path} />`](#redirect-topath-)
|
||||
- [`<Router hook={hook} parser={fn} base={basepath} />`](#router-hookhook-parserfn-basebasepath-hrefsfn-)
|
||||
|
||||
- [FAQ and Code Recipes](#faq-and-code-recipes)
|
||||
- [I deploy my app to the subfolder. Can I specify a base path?](#i-deploy-my-app-to-the-subfolder-can-i-specify-a-base-path)
|
||||
- [How do I make a default route?](#how-do-i-make-a-default-route)
|
||||
- [How do I make a link active for the current route?](#how-do-i-make-a-link-active-for-the-current-route)
|
||||
- [Are strict routes supported?](#are-strict-routes-supported)
|
||||
- [Are relative routes and links supported?](#are-relative-routes-and-links-supported)
|
||||
- [Can I initiate navigation from outside a component?](#can-i-initiate-navigation-from-outside-a-component)
|
||||
- [Can I use _wouter_ in my TypeScript project?](#can-i-use-wouter-in-my-typescript-project)
|
||||
- [How can add animated route transitions?](#how-can-add-animated-route-transitions)
|
||||
- [Preact support?](#preact-support)
|
||||
- [Server-side Rendering support (SSR)?](#server-side-rendering-support-ssr)
|
||||
- [How do I configure the router to render a specific route in tests?](#how-do-i-configure-the-router-to-render-a-specific-route-in-tests)
|
||||
- [1KB is too much, I can't afford it!](#1kb-is-too-much-i-cant-afford-it)
|
||||
- [Acknowledgements](#acknowledgements)
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, add wouter to your project.
|
||||
|
||||
```bash
|
||||
npm i wouter
|
||||
```
|
||||
|
||||
Or, if you're using Preact the use the following command [`npm i wouter-preact`](#preact-support).
|
||||
|
||||
Check out this simple demo app below. It doesn't cover hooks and other features such as nested routing, but it's a good starting point for those who are migrating from React Router.
|
||||
|
||||
```js
|
||||
import { Link, Route, Switch } from "wouter";
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<Link href="/users/1">Profile</Link>
|
||||
|
||||
<Route path="/about">About Us</Route>
|
||||
|
||||
{/*
|
||||
Routes below are matched exclusively -
|
||||
the first matched route gets rendered
|
||||
*/}
|
||||
<Switch>
|
||||
<Route path="/inbox" component={InboxPage} />
|
||||
|
||||
<Route path="/users/:name">
|
||||
{(params) => <>Hello, {params.name}!</>}
|
||||
</Route>
|
||||
|
||||
{/* Default route in a switch */}
|
||||
<Route>404: No such page!</Route>
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
```
|
||||
|
||||
### Browser Support
|
||||
|
||||
This library is designed for **ES2020+** compatibility. If you need to support older browsers, make sure that you transpile `node_modules`. Additionally, the minimum supported TypeScript version is 4.1 in order to support route parameter inference.
|
||||
|
||||
## Wouter API
|
||||
|
||||
Wouter comes with three kinds of APIs: low-level **standalone location hooks**, hooks for **routing and pattern matching** and more traditional **component-based
|
||||
API** similar to React Router's one.
|
||||
|
||||
You are free to choose whatever works for you: use location hooks when you want to keep your app as small as
|
||||
possible and don't need pattern matching; use routing hooks when you want to build custom routing components; or if you're building a traditional app
|
||||
with pages and navigation — components might come in handy.
|
||||
|
||||
Check out also [FAQ and Code Recipes](#faq-and-code-recipes) for more advanced things like active
|
||||
links, default routes, server-side rendering etc.
|
||||
|
||||
### The list of methods available
|
||||
|
||||
**Location Hooks**
|
||||
|
||||
These can be used separately from the main module and have an interface similar to `useState`. These hooks don't support nesting, base path, route matching.
|
||||
|
||||
- **[`import { useBrowserLocation } from "wouter/use-browser-location"`](https://github.com/molefrog/wouter/blob/v3/packages/wouter/src/use-browser-location.js)** —
|
||||
allows to manipulate current location in the browser's address bar, a tiny wrapper around the History API.
|
||||
- **[`import { useHashLocation } from "wouter/use-hash-location"`](https://github.com/molefrog/wouter/blob/v3/packages/wouter/src/use-hash-location.js)** — similarly, gets location from the hash part of the address, i.e. the string after a `#`.
|
||||
- **[`import { memoryLocation } from "wouter/memory-location"`](#uselocation-working-with-the-history)** — an in-memory location hook with history support, external navigation and immutable mode for testing. **Note** the module name because it is a high-order hook. See how memory location can be used in [testing](#how-do-i-configure-the-router-to-render-a-specific-route-in-tests).
|
||||
|
||||
**Routing Hooks**
|
||||
|
||||
Import from `wouter` module.
|
||||
|
||||
- **[`useRoute`](#useroute-the-power-of-hooks)** — shows whether or not current page matches the
|
||||
pattern provided.
|
||||
- **[`useLocation`](#uselocation-working-with-the-history)** — allows to manipulate current
|
||||
router's location, by default subscribes to browser location. **Note:** this isn't the same as `useBrowserLocation`, read below.
|
||||
- **[`useParams`](#useparams-extracting-matched-parameters)** — returns an object with parameters matched from the closest route.
|
||||
- **[`useSearch`](#usesearch-query-strings)** — returns a search string – everything that goes after the `?`.
|
||||
- **[`useRouter`](#userouter-accessing-the-router-object)** — returns a global router object that
|
||||
holds the configuration. Only use it if you want to customize the routing.
|
||||
|
||||
**Components**
|
||||
|
||||
Import from `wouter` module.
|
||||
|
||||
- **[`<Route />`](#route-pathpattern-)** — conditionally renders a component based on a pattern.
|
||||
- **[`<Link />`](#link-hrefpath-)** — wraps `<a>`, allows to perfom a navigation.
|
||||
- **[`<Switch />`](#switch-)** — exclusive routing, only renders the first matched route.
|
||||
- **[`<Redirect />`](#redirect-topath-)** — when rendered, performs an immediate navigation.
|
||||
- **[`<Router />`](#router-hookhook-matchermatchfn-basebasepath-)** — an optional top-level
|
||||
component for advanced routing configuration.
|
||||
|
||||
## Hooks API
|
||||
|
||||
### `useRoute`: route matching and parameters
|
||||
|
||||
Checks if the current location matches the pattern provided and returns an object with parameters. This is powered by a wonderful [`regexparam`](https://github.com/lukeed/regexparam) library, so all its pattern syntax is fully supported.
|
||||
|
||||
You can use `useRoute` to perform manual routing or implement custom logic, such as route transitions, etc.
|
||||
|
||||
```js
|
||||
import { useRoute } from "wouter";
|
||||
|
||||
const Users = () => {
|
||||
// `match` is a boolean
|
||||
const [match, params] = useRoute("/users/:name");
|
||||
|
||||
if (match) {
|
||||
return <>Hello, {params.name}!</>;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
A quick cheatsheet of what types of segments are supported:
|
||||
|
||||
```js
|
||||
useRoute("/app/:page");
|
||||
useRoute("/app/:page/:section");
|
||||
|
||||
// optional parameter, matches "/en/home" and "/home"
|
||||
useRoute("/:locale?/home");
|
||||
|
||||
// suffixes
|
||||
useRoute("/movies/:title.(mp4|mov)");
|
||||
|
||||
// wildcards, matches "/app", "/app-1", "/app/home"
|
||||
useRoute("/app*");
|
||||
|
||||
// optional wildcards, matches "/orders", "/orders/"
|
||||
// and "/orders/completed/list"
|
||||
useRoute("/orders/*?");
|
||||
|
||||
// regex for matching complex patterns,
|
||||
// matches "/hello:123"
|
||||
useRoute(/^[/]([a-z]+):([0-9]+)[/]?$/);
|
||||
// and with named capture groups
|
||||
useRoute(/^[/](?<word>[a-z]+):(?<num>[0-9]+)[/]?$/);
|
||||
```
|
||||
|
||||
The second item in the pair `params` is an object with parameters or null if there was no match. For wildcard segments the parameter name is `"*"`:
|
||||
|
||||
```js
|
||||
// wildcards, matches "/app", "/app-1", "/app/home"
|
||||
const [match, params] = useRoute("/app*");
|
||||
|
||||
if (match) {
|
||||
// "/home" for "/app/home"
|
||||
const page = params["*"];
|
||||
}
|
||||
```
|
||||
|
||||
### `useLocation`: working with the history
|
||||
|
||||
To get the current path and navigate between pages, call the `useLocation` hook. Similarly to `useState`, it returns a value and a setter: the component will re-render when the location changes and by calling `navigate` you can update this value and perform navigation.
|
||||
|
||||
By default, it uses `useBrowserLocation` under the hood, though you can configure this in a top-level `Router` component (for example, if you decide at some point to switch to a hash-based routing). `useLocation` will also return scoped path when used within nested routes or with base path setting.
|
||||
|
||||
```js
|
||||
import { useLocation } from "wouter";
|
||||
|
||||
const CurrentLocation = () => {
|
||||
const [location, setLocation] = useLocation();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{`The current page is: ${location}`}
|
||||
<a onClick={() => setLocation("/somewhere")}>Click to update</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
All the components internally call the `useLocation` hook.
|
||||
|
||||
#### Additional navigation parameters
|
||||
|
||||
The setter method of `useLocation` can also accept an optional object with parameters to control how
|
||||
the navigation update will happen.
|
||||
|
||||
When browser location is used (default), `useLocation` hook accepts `replace` flag to tell the hook to modify the current
|
||||
history entry instead of adding a new one. It is the same as calling `replaceState`.
|
||||
|
||||
```jsx
|
||||
const [location, navigate] = useLocation();
|
||||
|
||||
navigate("/jobs"); // `pushState` is used
|
||||
navigate("/home", { replace: true }); // `replaceState` is used
|
||||
```
|
||||
|
||||
Additionally, you can provide a `state` option to update `history.state` while navigating:
|
||||
|
||||
```jsx
|
||||
navigate("/home", { state: { modal: "promo" } });
|
||||
|
||||
history.state; // { modal: "promo" }
|
||||
```
|
||||
|
||||
#### Customizing the location hook
|
||||
|
||||
By default, **wouter** uses `useLocation` hook that reacts to `pushState` and `replaceState`
|
||||
navigation via `useBrowserLocation`.
|
||||
|
||||
To customize this, wrap your app in a `Router` component:
|
||||
|
||||
```js
|
||||
import { Router, Route } from "wouter";
|
||||
import { useHashLocation } from "wouter/use-hash-location";
|
||||
|
||||
const App = () => (
|
||||
<Router hook={useHashLocation}>
|
||||
<Route path="/about" component={About} />
|
||||
...
|
||||
</Router>
|
||||
);
|
||||
```
|
||||
|
||||
Because these hooks have return values similar to `useState`, it is easy and fun to build your own location hooks: `useCrossTabLocation`, `useLocalStorage`, `useMicroFrontendLocation` and whatever routing logic you want to support in the app. Give it a try!
|
||||
|
||||
### `useParams`: extracting matched parameters
|
||||
|
||||
This hook allows you to access the parameters exposed through [matching dynamic segments](#matching-dynamic-segments). Internally, we simply wrap your components in a context provider allowing you to access this data anywhere within the `Route` component.
|
||||
|
||||
This allows you to avoid "prop drilling" when dealing with deeply nested components within the route. **Note:** `useParams` will only extract parameters from the closest parent route.
|
||||
|
||||
```js
|
||||
import { Route, useParams } from "wouter";
|
||||
|
||||
const User = () => {
|
||||
const params = useParams();
|
||||
|
||||
params.id; // "1"
|
||||
|
||||
// alternatively, use the index to access the prop
|
||||
params[0]; // "1"
|
||||
};
|
||||
|
||||
<Route path="/user/:id" component={User}> />
|
||||
```
|
||||
|
||||
It is the same for regex paths. Capture groups can be accessed by their index, or if there is a named capture group, that can be used instead.
|
||||
|
||||
```js
|
||||
import { Route, useParams } from "wouter";
|
||||
|
||||
const User = () => {
|
||||
const params = useParams();
|
||||
|
||||
params.id; // "1"
|
||||
params[0]; // "1"
|
||||
};
|
||||
|
||||
<Route path={/^[/]user[/](?<id>[0-9]+)[/]?$/} component={User}> />
|
||||
```
|
||||
|
||||
### `useSearch`: query strings
|
||||
|
||||
Use this hook to get the current search (query) string value. It will cause your component to re-render only when the string itself and not the full location updates. The search string returned **does not** contain a `?` character.
|
||||
|
||||
```jsx
|
||||
import { useSearch } from "wouter";
|
||||
|
||||
// returns "tab=settings&id=1"
|
||||
// the hook for extracting search parameters is coming soon!
|
||||
const searchString = useSearch();
|
||||
```
|
||||
|
||||
For the SSR, use `ssrSearch` prop passed to the router.
|
||||
|
||||
```jsx
|
||||
<Router ssrSearch={request.search}>{/* SSR! */}</Router>
|
||||
```
|
||||
|
||||
Refer to [Server-Side Rendering](#server-side-rendering-support-ssr) for more info on rendering and hydration.
|
||||
|
||||
### `useRouter`: accessing the router object
|
||||
|
||||
If you're building advanced integration, for example custom location hook, you might want to get
|
||||
access to the global router object. Router is a simple object that holds routing options that you configure in the `Router` component.
|
||||
|
||||
```js
|
||||
import { useRouter } from "wouter";
|
||||
|
||||
const Custom = () => {
|
||||
const router = useRouter();
|
||||
|
||||
router.hook; // `useBrowserLocation` by default
|
||||
router.base; // "/app"
|
||||
};
|
||||
|
||||
const App = () => (
|
||||
<Router base="/app">
|
||||
<Custom />
|
||||
</Router>
|
||||
);
|
||||
```
|
||||
|
||||
## Component API
|
||||
|
||||
### `<Route path={pattern} />`
|
||||
|
||||
`Route` represents a piece of the app that is rendered conditionally based on a pattern `path`. Pattern has the same syntax as the argument you pass to [`useRoute`](#useroute-route-matching-and-parameters).
|
||||
|
||||
The library provides multiple ways to declare a route's body:
|
||||
|
||||
```js
|
||||
import { Route } from "wouter";
|
||||
|
||||
// simple form
|
||||
<Route path="/home"><Home /></Route>
|
||||
|
||||
// render-prop style
|
||||
<Route path="/users/:id">
|
||||
{params => <UserPage id={params.id} />}
|
||||
</Route>
|
||||
|
||||
// the `params` prop will be passed down to <Orders />
|
||||
<Route path="/orders/:status" component={Orders} />
|
||||
```
|
||||
|
||||
A route with no path is considered to always match, and it is the same as `<Route path="*" />`. When developing your app, use this trick to peek at the route's content without navigation.
|
||||
|
||||
```diff
|
||||
-<Route path="/some/page">
|
||||
+<Route>
|
||||
{/* Strip out the `path` to make this visible */}
|
||||
</Route>
|
||||
```
|
||||
|
||||
#### Route Nesting
|
||||
|
||||
Nesting is a core feature of wouter and can be enabled on a route via the `nest` prop. When this prop is present, the route matches everything that starts with a given pattern and it creates a nested routing context. All child routes will receive location relative to that pattern.
|
||||
|
||||
Let's take a look at this example:
|
||||
|
||||
```js
|
||||
<Route path="/app" nest>
|
||||
<Route path="/users/:id" nest>
|
||||
<Route path="/orders" />
|
||||
</Route>
|
||||
</Route>
|
||||
```
|
||||
|
||||
1. This first route will be active for all paths that start with `/app`, this is equivalent to having a base path in your app.
|
||||
|
||||
2. The second one uses dynamic pattern to match paths like `/app/user/1`, `/app/user/1/anything` and so on.
|
||||
|
||||
3. Finally, the inner-most route will only work for paths that look like `/app/users/1/orders`. The match is strict, since that route does not have a `nest` prop and it works as usual.
|
||||
|
||||
If you call `useLocation()` inside the last route, it will return `/orders` and not `/app/users/1/orders`. This creates a nice isolation and it makes it easier to make changes to parent route without worrying that the rest of the app will stop working. If you need to navigate to a top-level page however, you can use a prefix `~` to refer to an absolute path:
|
||||
|
||||
```js
|
||||
<Route path="/payments" nest>
|
||||
<Route path="/all">
|
||||
<Link to="~/home">Back to Home</Link>
|
||||
</Route>
|
||||
</Route>
|
||||
```
|
||||
|
||||
**Note:** The `nest` prop does not alter the regex passed into regex paths.
|
||||
Instead, the `nest` prop will only determine if nested routes will match against the rest of path or the same path.
|
||||
To make a strict path regex, use a regex pattern like `/^[/](your pattern)[/]?$/` (this matches an optional end slash and the end of the string).
|
||||
To make a nestable regex, use a regex pattern like `/^[/](your pattern)(?=$|[/])/` (this matches either the end of the string or a slash for future segments).
|
||||
|
||||
### `<Link href={path} />`
|
||||
|
||||
Link component renders an `<a />` element that, when clicked, performs a navigation.
|
||||
|
||||
```js
|
||||
import { Link } from "wouter"
|
||||
|
||||
<Link href="/">Home</Link>
|
||||
|
||||
// `to` is an alias for `href`
|
||||
<Link to="/">Home</Link>
|
||||
|
||||
// all standard `a` props are proxied
|
||||
<Link href="/" className="link" aria-label="Go to homepage">Home</Link>
|
||||
|
||||
// all location hook options are supported
|
||||
<Link href="/" replace state={{ animate: true }} />
|
||||
```
|
||||
|
||||
Link will always wrap its children in an `<a />` tag, unless `asChild` prop is provided. Use this when you need to have a custom component that renders an `<a />` under the hood.
|
||||
|
||||
```jsx
|
||||
// use this instead
|
||||
<Link to="/" asChild>
|
||||
<UIKitLink />
|
||||
</Link>
|
||||
|
||||
// Remember, `UIKitLink` must implement an `onClick` handler
|
||||
// in order for navigation to work!
|
||||
```
|
||||
|
||||
When you pass a function as a `className` prop, it will be called with a boolean value indicating whether the link is active for the current route. You can use this to style active links (e.g. for links in navigation menu)
|
||||
|
||||
```jsx
|
||||
<Link className={(active) => (active ? "active" : "")}>Nav</Link>
|
||||
```
|
||||
|
||||
Read more about [active links here](#how-do-i-make-a-link-active-for-the-current-route).
|
||||
|
||||
### `<Switch />`
|
||||
|
||||
There are cases when you want to have an exclusive routing: to make sure that only one route is
|
||||
rendered at the time, even if the routes have patterns that overlap. That's what `Switch` does: it
|
||||
only renders **the first matching route**.
|
||||
|
||||
```js
|
||||
import { Route, Switch } from "wouter";
|
||||
|
||||
<Switch>
|
||||
<Route path="/orders/all" component={AllOrders} />
|
||||
<Route path="/orders/:status" component={Orders} />
|
||||
|
||||
{/*
|
||||
in wouter, any Route with empty path is considered always active.
|
||||
This can be used to achieve "default" route behaviour within Switch.
|
||||
Note: the order matters! See examples below.
|
||||
*/}
|
||||
<Route>This is rendered when nothing above has matched</Route>
|
||||
</Switch>;
|
||||
```
|
||||
|
||||
When no route in switch matches, the last empty `Route` will be used as a fallback. See [**FAQ and Code Recipes** section](#how-do-i-make-a-default-route) to read about default routes.
|
||||
|
||||
### `<Redirect to={path} />`
|
||||
|
||||
When mounted performs a redirect to a `path` provided. Uses `useLocation` hook internally to trigger
|
||||
the navigation inside of a `useEffect` block.
|
||||
|
||||
`Redirect` can also accept props for [customizing how navigation will be performed](#additional-navigation-parameters), for example for setting history state when navigating. These options are specific to the currently used location hook.
|
||||
|
||||
```jsx
|
||||
<Redirect to="/" />
|
||||
|
||||
// arbitrary state object
|
||||
<Redirect to="/" state={{ modal: true }} />
|
||||
|
||||
// use `replaceState`
|
||||
<Redirect to="/" replace />
|
||||
```
|
||||
|
||||
If you need more advanced logic for navigation, for example, to trigger the redirect inside of an
|
||||
event handler, consider using
|
||||
[`useLocation` hook instead](#uselocation-working-with-the-history):
|
||||
|
||||
```js
|
||||
import { useLocation } from "wouter";
|
||||
|
||||
const [location, setLocation] = useLocation();
|
||||
|
||||
fetchOrders().then((orders) => {
|
||||
setOrders(orders);
|
||||
setLocation("/app/orders");
|
||||
});
|
||||
```
|
||||
|
||||
### `<Router hook={hook} parser={fn} base={basepath} hrefs={fn} />`
|
||||
|
||||
Unlike _React Router_, routes in wouter **don't have to be wrapped in a top-level component**. An
|
||||
internal router object will be constructed on demand, so you can start writing your app without
|
||||
polluting it with a cascade of top-level providers. There are cases however, when the routing
|
||||
behaviour needs to be customized.
|
||||
|
||||
These cases include hash-based routing, basepath support, custom matcher function etc.
|
||||
|
||||
```jsx
|
||||
import { useHashLocation } from "wouter/use-hash-location";
|
||||
|
||||
<Router hook={useHashLocation} base="/app">
|
||||
{/* Your app goes here */}
|
||||
</Router>;
|
||||
```
|
||||
|
||||
A router is a simple object that holds the routing configuration options. You can always obtain this
|
||||
object using a [`useRouter` hook](#userouter-accessing-the-router-object). The list of currently
|
||||
available options:
|
||||
|
||||
- **`hook: () => [location: string, setLocation: fn]`** — is a React Hook function that subscribes
|
||||
to location changes. It returns a pair of current `location` string e.g. `/app/users` and a
|
||||
`setLocation` function for navigation. You can use this hook from any component of your app by
|
||||
calling [`useLocation()` hook](#uselocation-working-with-the-history). See [Customizing the location hook](#customizing-the-location-hook).
|
||||
|
||||
- **`searchHook: () => [search: string, setSearch: fn]`** — similar to `hook`, but for obtaining the [current search string](#usesearch-query-strings).
|
||||
|
||||
- **`base: string`** — an optional setting that allows to specify a base path, such as `/app`. All
|
||||
application routes will be relative to that path. To navigate out to an absolute path, prefix your path with an `~`. [See the FAQ](#are-relative-routes-and-links-supported).
|
||||
|
||||
- **`parser: (path: string, loose?: boolean) => { pattern, keys }`** — a pattern parsing
|
||||
function. Produces a RegExp for matching the current location against the user-defined patterns like
|
||||
`/app/users/:id`. Has the same interface as the [`parse`](https://github.com/lukeed/regexparam?tab=readme-ov-file#regexparamparseinput-regexp) function from `regexparam`. See [this example](#are-strict-routes-supported) that demonstrates custom parser feature.
|
||||
|
||||
- **`ssrPath: string`** and **`ssrSearch: string`** use these when [rendering your app on the server](#server-side-rendering-support-ssr).
|
||||
|
||||
- `hrefs: (href: boolean) => string` — a function for transforming `href` attribute of an `<a />` element rendered by `Link`. It is used to support hash-based routing. By default, `href` attribute is the same as the `href` or `to` prop of a `Link`. A location hook can also define a `hook.hrefs` property, in this case the `href` will be inferred.
|
||||
|
||||
## FAQ and Code Recipes
|
||||
|
||||
### I deploy my app to the subfolder. Can I specify a base path?
|
||||
|
||||
You can! Wrap your app with `<Router base="/app" />` component and that should do the trick:
|
||||
|
||||
```js
|
||||
import { Router, Route, Link } from "wouter";
|
||||
|
||||
const App = () => (
|
||||
<Router base="/app">
|
||||
{/* the link's href attribute will be "/app/users" */}
|
||||
<Link href="/users">Users</Link>
|
||||
|
||||
<Route path="/users">The current path is /app/users!</Route>
|
||||
</Router>
|
||||
);
|
||||
```
|
||||
|
||||
Calling `useLocation()` within a route in an app with base path will return a path scoped to the base. Meaning that when base is `"/app"` and pathname is `"/app/users"` the returned string is `"/users"`. Accordingly, calling `navigate` will automatically append the base to the path argument for you.
|
||||
|
||||
When you have multiple nested routers, base paths are inherited and stack up.
|
||||
|
||||
```js
|
||||
<Router base="/app">
|
||||
<Router base="/cms">
|
||||
<Route path="/users">Path is /app/cms/users!</Route>
|
||||
</Router>
|
||||
</Router>
|
||||
```
|
||||
|
||||
### How do I make a default route?
|
||||
|
||||
One of the common patterns in application routing is having a default route that will be shown as a
|
||||
fallback, in case no other route matches (for example, if you need to render 404 message). In
|
||||
**wouter** this can easily be done as a combination of `<Switch />` component and a default route:
|
||||
|
||||
```js
|
||||
import { Switch, Route } from "wouter";
|
||||
|
||||
<Switch>
|
||||
<Route path="/about">...</Route>
|
||||
<Route>404, Not Found!</Route>
|
||||
</Switch>;
|
||||
```
|
||||
|
||||
_Note:_ the order of switch children matters, default route should always come last.
|
||||
|
||||
If you want to have access to the matched segment of the path you can use wildcard parameters:
|
||||
|
||||
```js
|
||||
<Switch>
|
||||
<Route path="/users">...</Route>
|
||||
|
||||
{/* will match anything that starts with /users/, e.g. /users/foo, /users/1/edit etc. */}
|
||||
<Route path="/users/*">...</Route>
|
||||
|
||||
{/* will match everything else */}
|
||||
<Route path="*">
|
||||
{(params) => `404, Sorry the page ${params["*"]} does not exist!`}
|
||||
</Route>
|
||||
</Switch>
|
||||
```
|
||||
|
||||
**[▶ Demo Sandbox](https://codesandbox.io/s/wouter-v3-ts-8q532r)**
|
||||
|
||||
### How do I make a link active for the current route?
|
||||
|
||||
Instead of a regular `className` string, provide a function to use custom class when this link matches the current route. Note that it will always perform an exact match (i.e. `/users` will not be active for `/users/1`).
|
||||
|
||||
```jsx
|
||||
<Link className={(active) => (active ? "active" : "")}>Nav link</Link>
|
||||
```
|
||||
|
||||
If you need to control other props, such as `aria-current` or `style`, you can write your own `<Link />` wrapper
|
||||
and detect if the path is active by using the `useRoute` hook.
|
||||
|
||||
```js
|
||||
const [isActive] = useRoute(props.href);
|
||||
|
||||
return (
|
||||
<Link {...props} asChild>
|
||||
<a style={isActive ? { color: "red" } : {}}>{props.children}</a>
|
||||
</Link>
|
||||
);
|
||||
```
|
||||
|
||||
**[▶ Demo Sandbox](https://codesandbox.io/s/wouter-v3-ts-8q532r?file=/src/ActiveLink.tsx)**
|
||||
|
||||
### Are strict routes supported?
|
||||
|
||||
If a trailing slash is important for your app's routing, you could specify a custom parser. Parser is a method that takes a pattern string and returns a RegExp and an array of parsed key. It uses the signature of a [`parse`](https://github.com/lukeed/regexparam?tab=readme-ov-file#regexparamparseinput-regexp) function from `regexparam`.
|
||||
|
||||
Let's write a custom parser based on a popular [`path-to-regexp`](https://github.com/pillarjs/path-to-regexp) package that does support strict routes option.
|
||||
|
||||
```js
|
||||
import { pathToRegexp } from "path-to-regexp";
|
||||
|
||||
/**
|
||||
* Custom parser based on `pathToRegexp` with strict route option
|
||||
*/
|
||||
const strictParser = (path, loose) => {
|
||||
const keys = [];
|
||||
const pattern = pathToRegexp(path, keys, { strict: true, end: !loose });
|
||||
|
||||
return {
|
||||
pattern,
|
||||
// `pathToRegexp` returns some metadata about the keys,
|
||||
// we want to strip it to just an array of keys
|
||||
keys: keys.map((k) => k.name),
|
||||
};
|
||||
};
|
||||
|
||||
const App = () => (
|
||||
<Router parser={strictParser}>
|
||||
<Route path="/foo">...</Route>
|
||||
<Route path="/foo/">...</Route>
|
||||
</Router>
|
||||
);
|
||||
```
|
||||
|
||||
**[▶ Demo Sandbox](https://codesandbox.io/s/wouter-v3-strict-routes-w3xdtz)**
|
||||
|
||||
### Are relative routes and links supported?
|
||||
|
||||
Yes! Any route with `nest` prop present creates a nesting context. Keep in mind, that the location inside a nested route will be scoped.
|
||||
|
||||
```js
|
||||
const App = () => (
|
||||
<Router base="/app">
|
||||
<Route path="/dashboard" nest>
|
||||
{/* the href is "/app/dashboard/users" */}
|
||||
<Link to="/users" />
|
||||
|
||||
<Route path="/users">
|
||||
{/* Here `useLocation()` returns "/users"! */}
|
||||
</Route>
|
||||
</Route>
|
||||
</Router>
|
||||
);
|
||||
```
|
||||
|
||||
**[▶ Demo Sandbox](https://codesandbox.io/s/wouter-v3-nested-routes-l8p23s)**
|
||||
|
||||
### Can I initiate navigation from outside a component?
|
||||
|
||||
Yes, the `navigate` function is exposed from the `"wouter/use-browser-location"` module:
|
||||
|
||||
```js
|
||||
import { navigate } from "wouter/use-browser-location";
|
||||
|
||||
navigate("/", { replace: true });
|
||||
```
|
||||
|
||||
It's the same function that is used internally.
|
||||
|
||||
### Can I use _wouter_ in my TypeScript project?
|
||||
|
||||
Yes! Although the project isn't written in TypeScript, the type definition files are bundled with
|
||||
the package.
|
||||
|
||||
### How can add animated route transitions?
|
||||
|
||||
Let's take look at how wouter routes can be animated with [`framer-motion`](framer.com/motion).
|
||||
Animating enter transitions is easy, but exit transitions require a bit more work. We'll use the `AnimatePresence` component that will keep the page in the DOM until the exit animation is complete.
|
||||
|
||||
Unfortunately, `AnimatePresence` only animates its **direct children**, so this won't work:
|
||||
|
||||
```jsx
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
|
||||
export const MyComponent = () => (
|
||||
<AnimatePresence>
|
||||
{/* This will not work! `motion.div` is not a direct child */}
|
||||
<Route path="/">
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
/>
|
||||
</Route>
|
||||
</AnimatePresence>
|
||||
);
|
||||
```
|
||||
|
||||
The workaround is to match this route manually with `useRoute`:
|
||||
|
||||
```jsx
|
||||
export const MyComponent = ({ isVisible }) => {
|
||||
const [isMatch] = useRoute("/");
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isMatch && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
More complex examples involve using `useRoutes` hook (similar to how React Router does it), but wouter does not ship it out-of-the-box. Please refer to [this issue](https://github.com/molefrog/wouter/issues/414#issuecomment-1954192679) for the workaround.
|
||||
|
||||
### Preact support?
|
||||
|
||||
Preact exports are available through a separate package named `wouter-preact` (or within the
|
||||
`wouter/preact` namespace, however this method isn't recommended as it requires React as a peer
|
||||
dependency):
|
||||
|
||||
```diff
|
||||
- import { useRoute, Route, Switch } from "wouter";
|
||||
+ import { useRoute, Route, Switch } from "wouter-preact";
|
||||
```
|
||||
|
||||
You might need to ensure you have the latest version of
|
||||
[Preact X](https://github.com/preactjs/preact/releases/tag/10.0.0-alpha.0) with support for hooks.
|
||||
|
||||
**[▶ Demo Sandbox](https://codesandbox.io/s/wouter-preact-0lr3n)**
|
||||
|
||||
### Server-side Rendering support (SSR)?
|
||||
|
||||
In order to render your app on the server, you'll need to wrap your app with top-level Router and
|
||||
specify `ssrPath` prop (usually, derived from current request). Optionally, `Router` accepts `ssrSearch` parameter if need to have access to a search string on a server.
|
||||
|
||||
```js
|
||||
import { renderToString } from "react-dom/server";
|
||||
import { Router } from "wouter";
|
||||
|
||||
const handleRequest = (req, res) => {
|
||||
// top-level Router is mandatory in SSR mode
|
||||
const prerendered = renderToString(
|
||||
<Router ssrPath={req.path} ssrSearch={req.search}>
|
||||
<App />
|
||||
</Router>
|
||||
);
|
||||
|
||||
// respond with prerendered html
|
||||
};
|
||||
```
|
||||
|
||||
Tip: wouter can pre-fill `ssrSearch`, if `ssrPath` contains the `?` character. So these are equivalent:
|
||||
|
||||
```jsx
|
||||
<Router ssrPath="/goods?sort=asc" />;
|
||||
|
||||
// is the same as
|
||||
<Router ssrPath="/goods" ssrSearch="sort=asc" />;
|
||||
```
|
||||
|
||||
On the client, the static markup must be hydrated in order for your app to become interactive. Note
|
||||
that to avoid having hydration warnings, the JSX rendered on the client must match the one used by
|
||||
the server, so the `Router` component must be present.
|
||||
|
||||
```js
|
||||
import { hydrateRoot } from "react-dom/client";
|
||||
|
||||
const root = hydrateRoot(
|
||||
domNode,
|
||||
// during hydration, `ssrPath` is set to `location.pathname`,
|
||||
// `ssrSearch` set to `location.search` accordingly
|
||||
// so there is no need to explicitly specify them
|
||||
<Router>
|
||||
<App />
|
||||
</Router>
|
||||
);
|
||||
```
|
||||
|
||||
**[▶ Demo](https://github.com/molefrog/wultra)**
|
||||
|
||||
### How do I configure the router to render a specific route in tests?
|
||||
|
||||
Testing with wouter is no different from testing regular React apps. You often need a way to provide a fixture for the current location to render a specific route. This can be easily done by swapping the normal location hook with `memoryLocation`. It is an initializer function that returns a hook that you can then specify in a top-level `Router`.
|
||||
|
||||
```jsx
|
||||
import { render } from "@testing-library/react";
|
||||
import { memoryLocation } from "wouter/memory-location";
|
||||
|
||||
it("renders a user page", () => {
|
||||
// `static` option makes it immutable
|
||||
// even if you call `navigate` somewhere in the app location won't change
|
||||
const { hook } = memoryLocation({ path: "/user/2", static: true });
|
||||
|
||||
const { container } = render(
|
||||
<Router hook={hook}>
|
||||
<Route path="/user/:id">{(params) => <>User ID: {params.id}</>}</Route>
|
||||
</Router>
|
||||
);
|
||||
|
||||
expect(container.innerHTML).toBe("User ID: 2");
|
||||
});
|
||||
```
|
||||
|
||||
The hook can be configured to record navigation history. Additionally, it comes with a `navigate` function for external navigation.
|
||||
|
||||
```jsx
|
||||
it("performs a redirect", () => {
|
||||
const { hook, history, navigate } = memoryLocation({
|
||||
path: "/",
|
||||
// will store navigation history in `history`
|
||||
record: true,
|
||||
});
|
||||
|
||||
const { container } = render(
|
||||
<Router hook={hook}>
|
||||
<Switch>
|
||||
<Route path="/">Index</Route>
|
||||
<Route path="/orders">Orders</Route>
|
||||
|
||||
<Route>
|
||||
<Redirect to="/orders" />
|
||||
</Route>
|
||||
</Switch>
|
||||
</Router>
|
||||
);
|
||||
|
||||
expect(history).toStrictEqual(["/"]);
|
||||
|
||||
navigate("/unknown/route");
|
||||
|
||||
expect(container.innerHTML).toBe("Orders");
|
||||
expect(history).toStrictEqual(["/", "/unknown/route", "/orders"]);
|
||||
});
|
||||
```
|
||||
|
||||
### 1KB is too much, I can't afford it!
|
||||
|
||||
We've got some great news for you! If you're a minimalist bundle-size nomad and you need a damn
|
||||
simple routing in your app, you can just use bare location hooks. For example, `useBrowserLocation` hook which is only **650 bytes gzipped**
|
||||
and manually match the current location with it:
|
||||
|
||||
```js
|
||||
import { useBrowserLocation } from "wouter/use-browser-location";
|
||||
|
||||
const UsersRoute = () => {
|
||||
const [location] = useBrowserLocation();
|
||||
|
||||
if (location !== "/users") return null;
|
||||
|
||||
// render the route
|
||||
};
|
||||
```
|
||||
|
||||
Wouter's motto is **"Minimalist-friendly"**.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
Wouter illustrations and logos were made by [Katya Simacheva](https://simachevakatya.com/) and
|
||||
[Katya Vakulenko](https://katyavakulenko.com/). Thank you to **[@jeetiss](https://github.com/jeetiss)**
|
||||
and all the amazing [contributors](https://github.com/molefrog/wouter/graphs/contributors) for
|
||||
helping with the development.
|
||||
353
node_modules/wouter/esm/index.js
generated
vendored
Normal file
353
node_modules/wouter/esm/index.js
generated
vendored
Normal file
@ -0,0 +1,353 @@
|
||||
import { parse } from 'regexparam';
|
||||
import { useBrowserLocation, useSearch as useSearch$1 } from './use-browser-location.js';
|
||||
import { createContext, forwardRef, useEvent, isValidElement, cloneElement, createElement, useContext, useRef, useIsomorphicLayoutEffect, Fragment } from './react-deps.js';
|
||||
|
||||
/*
|
||||
* Transforms `path` into its relative `base` version
|
||||
* If base isn't part of the path provided returns absolute path e.g. `~/app`
|
||||
*/
|
||||
const _relativePath = (base, path) =>
|
||||
!path.toLowerCase().indexOf(base.toLowerCase())
|
||||
? path.slice(base.length) || "/"
|
||||
: "~" + path;
|
||||
|
||||
/**
|
||||
* When basepath is `undefined` or '/' it is ignored (we assume it's empty string)
|
||||
*/
|
||||
const baseDefaults = (base = "") => (base === "/" ? "" : base);
|
||||
|
||||
const absolutePath = (to, base) =>
|
||||
to[0] === "~" ? to.slice(1) : baseDefaults(base) + to;
|
||||
|
||||
const relativePath = (base = "", path) =>
|
||||
_relativePath(unescape(baseDefaults(base)), unescape(path));
|
||||
|
||||
/*
|
||||
* Removes leading question mark
|
||||
*/
|
||||
const stripQm = (str) => (str[0] === "?" ? str.slice(1) : str);
|
||||
|
||||
/*
|
||||
* decodes escape sequences such as %20
|
||||
*/
|
||||
const unescape = (str) => {
|
||||
try {
|
||||
return decodeURI(str);
|
||||
} catch (_e) {
|
||||
// fail-safe mode: if string can't be decoded do nothing
|
||||
return str;
|
||||
}
|
||||
};
|
||||
|
||||
const sanitizeSearch = (search) => unescape(stripQm(search));
|
||||
|
||||
/*
|
||||
* Router and router context. Router is a lightweight object that represents the current
|
||||
* routing options: how location is managed, base path etc.
|
||||
*
|
||||
* There is a default router present for most of the use cases, however it can be overridden
|
||||
* via the <Router /> component.
|
||||
*/
|
||||
|
||||
const defaultRouter = {
|
||||
hook: useBrowserLocation,
|
||||
searchHook: useSearch$1,
|
||||
parser: parse,
|
||||
base: "",
|
||||
// this option is used to override the current location during SSR
|
||||
ssrPath: undefined,
|
||||
ssrSearch: undefined,
|
||||
// customizes how `href` props are transformed for <Link />
|
||||
hrefs: (x) => x,
|
||||
};
|
||||
|
||||
const RouterCtx = createContext(defaultRouter);
|
||||
|
||||
// gets the closest parent router from the context
|
||||
const useRouter = () => useContext(RouterCtx);
|
||||
|
||||
/**
|
||||
* Parameters context. Used by `useParams()` to get the
|
||||
* matched params from the innermost `Route` component.
|
||||
*/
|
||||
|
||||
const Params0 = {},
|
||||
ParamsCtx = createContext(Params0);
|
||||
|
||||
const useParams = () => useContext(ParamsCtx);
|
||||
|
||||
/*
|
||||
* Part 1, Hooks API: useRoute and useLocation
|
||||
*/
|
||||
|
||||
// Internal version of useLocation to avoid redundant useRouter calls
|
||||
|
||||
const useLocationFromRouter = (router) => {
|
||||
const [location, navigate] = router.hook(router);
|
||||
|
||||
// the function reference should stay the same between re-renders, so that
|
||||
// it can be passed down as an element prop without any performance concerns.
|
||||
// (This is achieved via `useEvent`.)
|
||||
return [
|
||||
relativePath(router.base, location),
|
||||
useEvent((to, navOpts) => navigate(absolutePath(to, router.base), navOpts)),
|
||||
];
|
||||
};
|
||||
|
||||
const useLocation = () => useLocationFromRouter(useRouter());
|
||||
|
||||
const useSearch = () => {
|
||||
const router = useRouter();
|
||||
return sanitizeSearch(router.searchHook(router));
|
||||
};
|
||||
|
||||
const matchRoute = (parser, route, path, loose) => {
|
||||
// if the input is a regexp, skip parsing
|
||||
const { pattern, keys } =
|
||||
route instanceof RegExp
|
||||
? { keys: false, pattern: route }
|
||||
: parser(route || "*", loose);
|
||||
|
||||
// array destructuring loses keys, so this is done in two steps
|
||||
const result = pattern.exec(path) || [];
|
||||
|
||||
// when parser is in "loose" mode, `$base` is equal to the
|
||||
// first part of the route that matches the pattern
|
||||
// (e.g. for pattern `/a/:b` and path `/a/1/2/3` the `$base` is `a/1`)
|
||||
// we use this for route nesting
|
||||
const [$base, ...matches] = result;
|
||||
|
||||
return $base !== undefined
|
||||
? [
|
||||
true,
|
||||
|
||||
(() => {
|
||||
// for regex paths, `keys` will always be false
|
||||
|
||||
// an object with parameters matched, e.g. { foo: "bar" } for "/:foo"
|
||||
// we "zip" two arrays here to construct the object
|
||||
// ["foo"], ["bar"] → { foo: "bar" }
|
||||
const groups =
|
||||
keys !== false
|
||||
? Object.fromEntries(keys.map((key, i) => [key, matches[i]]))
|
||||
: result.groups;
|
||||
|
||||
// convert the array to an instance of object
|
||||
// this makes it easier to integrate with the existing param implementation
|
||||
let obj = { ...matches };
|
||||
|
||||
// merge named capture groups with matches array
|
||||
groups && Object.assign(obj, groups);
|
||||
|
||||
return obj;
|
||||
})(),
|
||||
|
||||
// the third value if only present when parser is in "loose" mode,
|
||||
// so that we can extract the base path for nested routes
|
||||
...(loose ? [$base] : []),
|
||||
]
|
||||
: [false, null];
|
||||
};
|
||||
|
||||
const useRoute = (pattern) =>
|
||||
matchRoute(useRouter().parser, pattern, useLocation()[0]);
|
||||
|
||||
/*
|
||||
* Part 2, Low Carb Router API: Router, Route, Link, Switch
|
||||
*/
|
||||
|
||||
const Router = ({ children, ...props }) => {
|
||||
// the router we will inherit from - it is the closest router in the tree,
|
||||
// unless the custom `hook` is provided (in that case it's the default one)
|
||||
const parent_ = useRouter();
|
||||
const parent = props.hook ? defaultRouter : parent_;
|
||||
|
||||
// holds to the context value: the router object
|
||||
let value = parent;
|
||||
|
||||
// when `ssrPath` contains a `?` character, we can extract the search from it
|
||||
const [path, search] = props.ssrPath?.split("?") ?? [];
|
||||
if (search) (props.ssrSearch = search), (props.ssrPath = path);
|
||||
|
||||
// hooks can define their own `href` formatter (e.g. for hash location)
|
||||
props.hrefs = props.hrefs ?? props.hook?.hrefs;
|
||||
|
||||
// what is happening below: to avoid unnecessary rerenders in child components,
|
||||
// we ensure that the router object reference is stable, unless there are any
|
||||
// changes that require reload (e.g. `base` prop changes -> all components that
|
||||
// get the router from the context should rerender, even if the component is memoized).
|
||||
// the expected behaviour is:
|
||||
//
|
||||
// 1) when the resulted router is no different from the parent, use parent
|
||||
// 2) if the custom `hook` prop is provided, we always inherit from the
|
||||
// default router instead. this resets all previously overridden options.
|
||||
// 3) when the router is customized here, it should stay stable between renders
|
||||
let ref = useRef({}),
|
||||
prev = ref.current,
|
||||
next = prev;
|
||||
|
||||
for (let k in parent) {
|
||||
const option =
|
||||
k === "base"
|
||||
? /* base is special case, it is appended to the parent's base */
|
||||
parent[k] + (props[k] || "")
|
||||
: props[k] || parent[k];
|
||||
|
||||
if (prev === next && option !== next[k]) {
|
||||
ref.current = next = { ...next };
|
||||
}
|
||||
|
||||
next[k] = option;
|
||||
|
||||
// the new router is no different from the parent, use parent
|
||||
if (option !== parent[k]) value = next;
|
||||
}
|
||||
|
||||
return createElement(RouterCtx.Provider, { value, children });
|
||||
};
|
||||
|
||||
const h_route = ({ children, component }, params) => {
|
||||
// React-Router style `component` prop
|
||||
if (component) return createElement(component, { params });
|
||||
|
||||
// support render prop or plain children
|
||||
return typeof children === "function" ? children(params) : children;
|
||||
};
|
||||
|
||||
// a hook to cache the params object between renders (if they are shallow equal)
|
||||
const useCachedParams = (value) => {
|
||||
let prev = useRef(Params0),
|
||||
curr = prev.current;
|
||||
|
||||
for (const k in value) if (value[k] !== curr[k]) curr = value;
|
||||
if (Object.keys(value).length === 0) curr = value;
|
||||
return (prev.current = curr);
|
||||
};
|
||||
|
||||
const Route = ({ path, nest, match, ...renderProps }) => {
|
||||
const router = useRouter();
|
||||
const [location] = useLocationFromRouter(router);
|
||||
|
||||
const [matches, routeParams, base] =
|
||||
// `match` is a special prop to give up control to the parent,
|
||||
// it is used by the `Switch` to avoid double matching
|
||||
match ?? matchRoute(router.parser, path, location, nest);
|
||||
|
||||
// when `routeParams` is `null` (there was no match), the argument
|
||||
// below becomes {...null} = {}, see the Object Spread specs
|
||||
// https://tc39.es/proposal-object-rest-spread/#AbstractOperations-CopyDataProperties
|
||||
const params = useCachedParams({ ...useParams(), ...routeParams });
|
||||
|
||||
if (!matches) return null;
|
||||
|
||||
const children = base
|
||||
? createElement(Router, { base }, h_route(renderProps, params))
|
||||
: h_route(renderProps, params);
|
||||
|
||||
return createElement(ParamsCtx.Provider, { value: params, children });
|
||||
};
|
||||
|
||||
const Link = forwardRef((props, ref) => {
|
||||
const router = useRouter();
|
||||
const [currentPath, navigate] = useLocationFromRouter(router);
|
||||
|
||||
const {
|
||||
to = "",
|
||||
href: targetPath = to,
|
||||
onClick: _onClick,
|
||||
asChild,
|
||||
children,
|
||||
className: cls,
|
||||
/* eslint-disable no-unused-vars */
|
||||
replace /* ignore nav props */,
|
||||
state /* ignore nav props */,
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
const onClick = useEvent((event) => {
|
||||
// ignores the navigation when clicked using right mouse button or
|
||||
// by holding a special modifier key: ctrl, command, win, alt, shift
|
||||
if (
|
||||
event.ctrlKey ||
|
||||
event.metaKey ||
|
||||
event.altKey ||
|
||||
event.shiftKey ||
|
||||
event.button !== 0
|
||||
)
|
||||
return;
|
||||
|
||||
_onClick?.(event);
|
||||
if (!event.defaultPrevented) {
|
||||
event.preventDefault();
|
||||
navigate(targetPath, props);
|
||||
}
|
||||
});
|
||||
|
||||
// handle nested routers and absolute paths
|
||||
const href = router.hrefs(
|
||||
targetPath[0] === "~" ? targetPath.slice(1) : router.base + targetPath,
|
||||
router // pass router as a second argument for convinience
|
||||
);
|
||||
|
||||
return asChild && isValidElement(children)
|
||||
? cloneElement(children, { onClick, href })
|
||||
: createElement("a", {
|
||||
...restProps,
|
||||
onClick,
|
||||
href,
|
||||
// `className` can be a function to apply the class if this link is active
|
||||
className: cls?.call ? cls(currentPath === targetPath) : cls,
|
||||
children,
|
||||
ref,
|
||||
});
|
||||
});
|
||||
|
||||
const flattenChildren = (children) =>
|
||||
Array.isArray(children)
|
||||
? children.flatMap((c) =>
|
||||
flattenChildren(c && c.type === Fragment ? c.props.children : c)
|
||||
)
|
||||
: [children];
|
||||
|
||||
const Switch = ({ children, location }) => {
|
||||
const router = useRouter();
|
||||
const [originalLocation] = useLocationFromRouter(router);
|
||||
|
||||
for (const element of flattenChildren(children)) {
|
||||
let match = 0;
|
||||
|
||||
if (
|
||||
isValidElement(element) &&
|
||||
// we don't require an element to be of type Route,
|
||||
// but we do require it to contain a truthy `path` prop.
|
||||
// this allows to use different components that wrap Route
|
||||
// inside of a switch, for example <AnimatedRoute />.
|
||||
(match = matchRoute(
|
||||
router.parser,
|
||||
element.props.path,
|
||||
location || originalLocation,
|
||||
element.props.nest
|
||||
))[0]
|
||||
)
|
||||
return cloneElement(element, { match });
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const Redirect = (props) => {
|
||||
const { to, href = to } = props;
|
||||
const [, navigate] = useLocation();
|
||||
const redirect = useEvent(() => navigate(to || href, props));
|
||||
|
||||
// redirect is guaranteed to be stable since it is returned from useEvent
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
redirect();
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export { Link, Redirect, Route, Router, Switch, matchRoute, useLocation, useParams, useRoute, useRouter, useSearch };
|
||||
57
node_modules/wouter/esm/memory-location.js
generated
vendored
Normal file
57
node_modules/wouter/esm/memory-location.js
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
import mitt from 'mitt';
|
||||
import { useSyncExternalStore } from './react-deps.js';
|
||||
|
||||
/**
|
||||
* In-memory location that supports navigation
|
||||
*/
|
||||
|
||||
const memoryLocation = ({
|
||||
path = "/",
|
||||
static: staticLocation,
|
||||
record,
|
||||
} = {}) => {
|
||||
let currentPath = path;
|
||||
const history = [currentPath];
|
||||
const emitter = mitt();
|
||||
|
||||
const navigateImplementation = (path, { replace = false } = {}) => {
|
||||
if (record) {
|
||||
if (replace) {
|
||||
history.splice(history.length - 1, 1, path);
|
||||
} else {
|
||||
history.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
currentPath = path;
|
||||
emitter.emit("navigate", path);
|
||||
};
|
||||
|
||||
const navigate = !staticLocation ? navigateImplementation : () => null;
|
||||
|
||||
const subscribe = (cb) => {
|
||||
emitter.on("navigate", cb);
|
||||
return () => emitter.off("navigate", cb);
|
||||
};
|
||||
|
||||
const useMemoryLocation = () => [
|
||||
useSyncExternalStore(subscribe, () => currentPath),
|
||||
navigate,
|
||||
];
|
||||
|
||||
function reset() {
|
||||
// clean history array with mutation to preserve link
|
||||
history.splice(0, history.length);
|
||||
|
||||
navigateImplementation(path);
|
||||
}
|
||||
|
||||
return {
|
||||
hook: useMemoryLocation,
|
||||
navigate,
|
||||
history: record ? history : undefined,
|
||||
reset: record ? reset : undefined,
|
||||
};
|
||||
};
|
||||
|
||||
export { memoryLocation };
|
||||
48
node_modules/wouter/esm/react-deps.js
generated
vendored
Normal file
48
node_modules/wouter/esm/react-deps.js
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
import * as React from 'react';
|
||||
export { Fragment, cloneElement, createContext, createElement, forwardRef, isValidElement, useContext, useRef, useState } from 'react';
|
||||
export { useSyncExternalStore } from 'use-sync-external-store/shim/index.js';
|
||||
|
||||
// React.useInsertionEffect is not available in React <18
|
||||
// This hack fixes a transpilation issue on some apps
|
||||
const useBuiltinInsertionEffect = React["useInsertion" + "Effect"];
|
||||
|
||||
// Copied from:
|
||||
// https://github.com/facebook/react/blob/main/packages/shared/ExecutionEnvironment.js
|
||||
const canUseDOM = !!(
|
||||
typeof window !== "undefined" &&
|
||||
typeof window.document !== "undefined" &&
|
||||
typeof window.document.createElement !== "undefined"
|
||||
);
|
||||
|
||||
// Copied from:
|
||||
// https://github.com/reduxjs/react-redux/blob/master/src/utils/useIsomorphicLayoutEffect.ts
|
||||
// "React currently throws a warning when using useLayoutEffect on the server.
|
||||
// To get around it, we can conditionally useEffect on the server (no-op) and
|
||||
// useLayoutEffect in the browser."
|
||||
const useIsomorphicLayoutEffect = canUseDOM
|
||||
? React.useLayoutEffect
|
||||
: React.useEffect;
|
||||
|
||||
// useInsertionEffect is already a noop on the server.
|
||||
// See: https://github.com/facebook/react/blob/main/packages/react-server/src/ReactFizzHooks.js
|
||||
const useInsertionEffect =
|
||||
useBuiltinInsertionEffect || useIsomorphicLayoutEffect;
|
||||
|
||||
// Userland polyfill while we wait for the forthcoming
|
||||
// https://github.com/reactjs/rfcs/blob/useevent/text/0000-useevent.md
|
||||
// Note: "A high-fidelity polyfill for useEvent is not possible because
|
||||
// there is no lifecycle or Hook in React that we can use to switch
|
||||
// .current at the right timing."
|
||||
// So we will have to make do with this "close enough" approach for now.
|
||||
const useEvent = (fn) => {
|
||||
const ref = React.useRef([fn, (...args) => ref[0](...args)]).current;
|
||||
// Per Dan Abramov: useInsertionEffect executes marginally closer to the
|
||||
// correct timing for ref synchronization than useLayoutEffect on React 18.
|
||||
// See: https://github.com/facebook/react/pull/25881#issuecomment-1356244360
|
||||
useInsertionEffect(() => {
|
||||
ref[0] = fn;
|
||||
});
|
||||
return ref[1];
|
||||
};
|
||||
|
||||
export { useEvent, useInsertionEffect, useIsomorphicLayoutEffect };
|
||||
83
node_modules/wouter/esm/use-browser-location.js
generated
vendored
Normal file
83
node_modules/wouter/esm/use-browser-location.js
generated
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
import { useSyncExternalStore } from './react-deps.js';
|
||||
|
||||
/**
|
||||
* History API docs @see https://developer.mozilla.org/en-US/docs/Web/API/History
|
||||
*/
|
||||
const eventPopstate = "popstate";
|
||||
const eventPushState = "pushState";
|
||||
const eventReplaceState = "replaceState";
|
||||
const eventHashchange = "hashchange";
|
||||
const events = [
|
||||
eventPopstate,
|
||||
eventPushState,
|
||||
eventReplaceState,
|
||||
eventHashchange,
|
||||
];
|
||||
|
||||
const subscribeToLocationUpdates = (callback) => {
|
||||
for (const event of events) {
|
||||
addEventListener(event, callback);
|
||||
}
|
||||
return () => {
|
||||
for (const event of events) {
|
||||
removeEventListener(event, callback);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const useLocationProperty = (fn, ssrFn) =>
|
||||
useSyncExternalStore(subscribeToLocationUpdates, fn, ssrFn);
|
||||
|
||||
const currentSearch = () => location.search;
|
||||
|
||||
const useSearch = ({ ssrSearch = "" } = {}) =>
|
||||
useLocationProperty(currentSearch, () => ssrSearch);
|
||||
|
||||
const currentPathname = () => location.pathname;
|
||||
|
||||
const usePathname = ({ ssrPath } = {}) =>
|
||||
useLocationProperty(
|
||||
currentPathname,
|
||||
ssrPath ? () => ssrPath : currentPathname
|
||||
);
|
||||
|
||||
const currentHistoryState = () => history.state;
|
||||
const useHistoryState = () =>
|
||||
useLocationProperty(currentHistoryState, () => null);
|
||||
|
||||
const navigate = (to, { replace = false, state = null } = {}) =>
|
||||
history[replace ? eventReplaceState : eventPushState](state, "", to);
|
||||
|
||||
// the 2nd argument of the `useBrowserLocation` return value is a function
|
||||
// that allows to perform a navigation.
|
||||
const useBrowserLocation = (opts = {}) => [usePathname(opts), navigate];
|
||||
|
||||
const patchKey = Symbol.for("wouter_v3");
|
||||
|
||||
// While History API does have `popstate` event, the only
|
||||
// proper way to listen to changes via `push/replaceState`
|
||||
// is to monkey-patch these methods.
|
||||
//
|
||||
// See https://stackoverflow.com/a/4585031
|
||||
if (typeof history !== "undefined" && typeof window[patchKey] === "undefined") {
|
||||
for (const type of [eventPushState, eventReplaceState]) {
|
||||
const original = history[type];
|
||||
// TODO: we should be using unstable_batchedUpdates to avoid multiple re-renders,
|
||||
// however that will require an additional peer dependency on react-dom.
|
||||
// See: https://github.com/reactwg/react-18/discussions/86#discussioncomment-1567149
|
||||
history[type] = function () {
|
||||
const result = original.apply(this, arguments);
|
||||
const event = new Event(type);
|
||||
event.arguments = arguments;
|
||||
|
||||
dispatchEvent(event);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
// patch history object only once
|
||||
// See: https://github.com/molefrog/wouter/issues/167
|
||||
Object.defineProperty(window, patchKey, { value: true });
|
||||
}
|
||||
|
||||
export { navigate, useBrowserLocation, useHistoryState, useLocationProperty, usePathname, useSearch };
|
||||
53
node_modules/wouter/esm/use-hash-location.js
generated
vendored
Normal file
53
node_modules/wouter/esm/use-hash-location.js
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
import { useSyncExternalStore } from './react-deps.js';
|
||||
|
||||
// array of callback subscribed to hash updates
|
||||
const listeners = {
|
||||
v: [],
|
||||
};
|
||||
|
||||
const onHashChange = () => listeners.v.forEach((cb) => cb());
|
||||
|
||||
// we subscribe to `hashchange` only once when needed to guarantee that
|
||||
// all listeners are called synchronously
|
||||
const subscribeToHashUpdates = (callback) => {
|
||||
if (listeners.v.push(callback) === 1)
|
||||
addEventListener("hashchange", onHashChange);
|
||||
|
||||
return () => {
|
||||
listeners.v = listeners.v.filter((i) => i !== callback);
|
||||
if (!listeners.v.length) removeEventListener("hashchange", onHashChange);
|
||||
};
|
||||
};
|
||||
|
||||
// leading '#' is ignored, leading '/' is optional
|
||||
const currentHashLocation = () => "/" + location.hash.replace(/^#?\/?/, "");
|
||||
|
||||
const navigate = (to, { state = null } = {}) => {
|
||||
// calling `replaceState` allows us to set the history
|
||||
// state without creating an extra entry
|
||||
const [hash, search] = to.replace(/^#?\/?/, "").split("?");
|
||||
|
||||
history.replaceState(
|
||||
state,
|
||||
"",
|
||||
// keep the current pathname, but replace query string and hash
|
||||
location.pathname +
|
||||
(search ? `?${search}` : location.search) +
|
||||
// update location hash, this will cause `hashchange` event to fire
|
||||
// normalise the value before updating, so it's always preceeded with "#/"
|
||||
(location.hash = `#/${hash}`)
|
||||
);
|
||||
};
|
||||
|
||||
const useHashLocation = ({ ssrPath = "/" } = {}) => [
|
||||
useSyncExternalStore(
|
||||
subscribeToHashUpdates,
|
||||
currentHashLocation,
|
||||
() => ssrPath
|
||||
),
|
||||
navigate,
|
||||
];
|
||||
|
||||
useHashLocation.hrefs = (href) => "#" + href;
|
||||
|
||||
export { navigate, useHashLocation };
|
||||
75
node_modules/wouter/package.json
generated
vendored
Normal file
75
node_modules/wouter/package.json
generated
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
{
|
||||
"name": "wouter",
|
||||
"version": "3.3.5",
|
||||
"description": "Minimalist-friendly ~1.5KB router for React",
|
||||
"type": "module",
|
||||
"keywords": [
|
||||
"react",
|
||||
"preact",
|
||||
"router",
|
||||
"tiny",
|
||||
"routing",
|
||||
"hooks",
|
||||
"useLocation"
|
||||
],
|
||||
"files": [
|
||||
"esm",
|
||||
"types/**/*.d.ts",
|
||||
"types/*.d.ts"
|
||||
],
|
||||
"main": "esm/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/index.d.ts",
|
||||
"default": "./esm/index.js"
|
||||
},
|
||||
"./use-browser-location": {
|
||||
"types": "./types/use-browser-location.d.ts",
|
||||
"default": "./esm/use-browser-location.js"
|
||||
},
|
||||
"./use-hash-location": {
|
||||
"types": "./types/use-hash-location.d.ts",
|
||||
"default": "./esm/use-hash-location.js"
|
||||
},
|
||||
"./memory-location": {
|
||||
"types": "./types/memory-location.d.ts",
|
||||
"default": "./esm/memory-location.js"
|
||||
}
|
||||
},
|
||||
"types": "types/index.d.ts",
|
||||
"typesVersions": {
|
||||
">=4.1": {
|
||||
"types/index.d.ts": [
|
||||
"types/index.d.ts"
|
||||
],
|
||||
"use-browser-location": [
|
||||
"types/use-browser-location.d.ts"
|
||||
],
|
||||
"use-hash-location": [
|
||||
"types/use-hash-location.d.ts"
|
||||
],
|
||||
"memory-location": [
|
||||
"types/memory-location.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rollup -c",
|
||||
"watch": "rollup -c -w",
|
||||
"prepublishOnly": "npm run build && cp ../../README.md ."
|
||||
},
|
||||
"author": "Alexey Taktarov <molefrog@gmail.com>",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/molefrog/wouter.git"
|
||||
},
|
||||
"license": "Unlicense",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"mitt": "^3.0.1",
|
||||
"regexparam": "^3.0.0",
|
||||
"use-sync-external-store": "^1.0.0"
|
||||
}
|
||||
}
|
||||
214
node_modules/wouter/types/index.d.ts
generated
vendored
Normal file
214
node_modules/wouter/types/index.d.ts
generated
vendored
Normal file
@ -0,0 +1,214 @@
|
||||
// Minimum TypeScript Version: 4.1
|
||||
|
||||
// tslint:disable:no-unnecessary-generics
|
||||
|
||||
import {
|
||||
AnchorHTMLAttributes,
|
||||
FunctionComponent,
|
||||
RefAttributes,
|
||||
ComponentType,
|
||||
ReactNode,
|
||||
ReactElement,
|
||||
MouseEventHandler,
|
||||
} from "react";
|
||||
|
||||
import {
|
||||
Path,
|
||||
PathPattern,
|
||||
BaseLocationHook,
|
||||
HookReturnValue,
|
||||
HookNavigationOptions,
|
||||
BaseSearchHook,
|
||||
} from "./location-hook.js";
|
||||
import {
|
||||
BrowserLocationHook,
|
||||
BrowserSearchHook,
|
||||
} from "./use-browser-location.js";
|
||||
|
||||
import { Parser, RouterObject, RouterOptions } from "./router.js";
|
||||
|
||||
// these files only export types, so we can re-export them as-is
|
||||
// in TS 5.0 we'll be able to use `export type * from ...`
|
||||
export * from "./location-hook.js";
|
||||
export * from "./router.js";
|
||||
|
||||
import { RouteParams } from "regexparam";
|
||||
|
||||
export type StringRouteParams<T extends string> = RouteParams<T> & {
|
||||
[param: number]: string | undefined;
|
||||
};
|
||||
export type RegexRouteParams = { [key: string | number]: string | undefined };
|
||||
|
||||
/**
|
||||
* Route patterns and parameters
|
||||
*/
|
||||
export interface DefaultParams {
|
||||
readonly [paramName: string | number]: string | undefined;
|
||||
}
|
||||
|
||||
export type Params<T extends DefaultParams = DefaultParams> = T;
|
||||
|
||||
export type MatchWithParams<T extends DefaultParams = DefaultParams> = [
|
||||
true,
|
||||
Params<T>
|
||||
];
|
||||
export type NoMatch = [false, null];
|
||||
export type Match<T extends DefaultParams = DefaultParams> =
|
||||
| MatchWithParams<T>
|
||||
| NoMatch;
|
||||
|
||||
/*
|
||||
* Components: <Route />
|
||||
*/
|
||||
|
||||
export interface RouteComponentProps<T extends DefaultParams = DefaultParams> {
|
||||
params: T;
|
||||
}
|
||||
|
||||
export interface RouteProps<
|
||||
T extends DefaultParams | undefined = undefined,
|
||||
RoutePath extends PathPattern = PathPattern
|
||||
> {
|
||||
children?:
|
||||
| ((
|
||||
params: T extends DefaultParams
|
||||
? T
|
||||
: RoutePath extends string
|
||||
? StringRouteParams<RoutePath>
|
||||
: RegexRouteParams
|
||||
) => ReactNode)
|
||||
| ReactNode;
|
||||
path?: RoutePath;
|
||||
component?: ComponentType<
|
||||
RouteComponentProps<
|
||||
T extends DefaultParams
|
||||
? T
|
||||
: RoutePath extends string
|
||||
? StringRouteParams<RoutePath>
|
||||
: RegexRouteParams
|
||||
>
|
||||
>;
|
||||
nest?: boolean;
|
||||
}
|
||||
|
||||
export function Route<
|
||||
T extends DefaultParams | undefined = undefined,
|
||||
RoutePath extends PathPattern = PathPattern
|
||||
>(props: RouteProps<T, RoutePath>): ReturnType<FunctionComponent>;
|
||||
|
||||
/*
|
||||
* Components: <Link /> & <Redirect />
|
||||
*/
|
||||
|
||||
export type NavigationalProps<
|
||||
H extends BaseLocationHook = BrowserLocationHook
|
||||
> = ({ to: Path; href?: never } | { href: Path; to?: never }) &
|
||||
HookNavigationOptions<H>;
|
||||
|
||||
export type RedirectProps<H extends BaseLocationHook = BrowserLocationHook> =
|
||||
NavigationalProps<H> & {
|
||||
children?: never;
|
||||
};
|
||||
|
||||
export function Redirect<H extends BaseLocationHook = BrowserLocationHook>(
|
||||
props: RedirectProps<H>,
|
||||
context?: any
|
||||
): null;
|
||||
|
||||
type AsChildProps<ComponentProps, DefaultElementProps> =
|
||||
| ({ asChild?: false } & DefaultElementProps)
|
||||
| ({ asChild: true } & ComponentProps);
|
||||
|
||||
type HTMLLinkAttributes = Omit<
|
||||
AnchorHTMLAttributes<HTMLAnchorElement>,
|
||||
"className"
|
||||
> & {
|
||||
className?: string | undefined | ((isActive: boolean) => string | undefined);
|
||||
};
|
||||
|
||||
export type LinkProps<H extends BaseLocationHook = BrowserLocationHook> =
|
||||
NavigationalProps<H> &
|
||||
AsChildProps<
|
||||
{ children: ReactElement; onClick?: MouseEventHandler },
|
||||
HTMLLinkAttributes & RefAttributes<HTMLAnchorElement>
|
||||
>;
|
||||
|
||||
export function Link<H extends BaseLocationHook = BrowserLocationHook>(
|
||||
props: LinkProps<H>,
|
||||
context?: any
|
||||
): ReturnType<FunctionComponent>;
|
||||
|
||||
/*
|
||||
* Components: <Switch />
|
||||
*/
|
||||
|
||||
export interface SwitchProps {
|
||||
location?: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
export const Switch: FunctionComponent<SwitchProps>;
|
||||
|
||||
/*
|
||||
* Components: <Router />
|
||||
*/
|
||||
|
||||
export type RouterProps = RouterOptions & {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const Router: FunctionComponent<RouterProps>;
|
||||
|
||||
/*
|
||||
* Hooks
|
||||
*/
|
||||
|
||||
export function useRouter(): RouterObject;
|
||||
|
||||
export function useRoute<
|
||||
T extends DefaultParams | undefined = undefined,
|
||||
RoutePath extends PathPattern = PathPattern
|
||||
>(
|
||||
pattern: RoutePath
|
||||
): Match<
|
||||
T extends DefaultParams
|
||||
? T
|
||||
: RoutePath extends string
|
||||
? StringRouteParams<RoutePath>
|
||||
: RegexRouteParams
|
||||
>;
|
||||
|
||||
export function useLocation<
|
||||
H extends BaseLocationHook = BrowserLocationHook
|
||||
>(): HookReturnValue<H>;
|
||||
|
||||
export function useSearch<
|
||||
H extends BaseSearchHook = BrowserSearchHook
|
||||
>(): ReturnType<H>;
|
||||
|
||||
export function useParams<T = undefined>(): T extends string
|
||||
? StringRouteParams<T>
|
||||
: T extends undefined
|
||||
? DefaultParams
|
||||
: T;
|
||||
|
||||
/*
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
export function matchRoute<
|
||||
T extends DefaultParams | undefined = undefined,
|
||||
RoutePath extends PathPattern = PathPattern
|
||||
>(
|
||||
parser: Parser,
|
||||
pattern: RoutePath,
|
||||
path: string,
|
||||
loose?: boolean
|
||||
): Match<
|
||||
T extends DefaultParams
|
||||
? T
|
||||
: RoutePath extends string
|
||||
? StringRouteParams<RoutePath>
|
||||
: RegexRouteParams
|
||||
>;
|
||||
|
||||
// tslint:enable:no-unnecessary-generics
|
||||
37
node_modules/wouter/types/location-hook.d.ts
generated
vendored
Normal file
37
node_modules/wouter/types/location-hook.d.ts
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Foundation: useLocation and paths
|
||||
*/
|
||||
|
||||
export type Path = string;
|
||||
|
||||
export type PathPattern = string | RegExp;
|
||||
|
||||
export type SearchString = string;
|
||||
|
||||
// the base useLocation hook type. Any custom hook (including the
|
||||
// default one) should inherit from it.
|
||||
export type BaseLocationHook = (
|
||||
...args: any[]
|
||||
) => [Path, (path: Path, ...args: any[]) => any];
|
||||
|
||||
export type BaseSearchHook = (...args: any[]) => SearchString;
|
||||
|
||||
/*
|
||||
* Utility types that operate on hook
|
||||
*/
|
||||
|
||||
// Returns the type of the location tuple of the given hook.
|
||||
export type HookReturnValue<H extends BaseLocationHook> = ReturnType<H>;
|
||||
|
||||
// Utility type that allows us to handle cases like `any` and `never`
|
||||
type EmptyInterfaceWhenAnyOrNever<T> = 0 extends 1 & T
|
||||
? {}
|
||||
: [T] extends [never]
|
||||
? {}
|
||||
: T;
|
||||
|
||||
// Returns the type of the navigation options that hook's push function accepts.
|
||||
export type HookNavigationOptions<H extends BaseLocationHook> =
|
||||
EmptyInterfaceWhenAnyOrNever<
|
||||
NonNullable<Parameters<HookReturnValue<H>[1]>[1]> // get's the second argument of a tuple returned by the hook
|
||||
>;
|
||||
20
node_modules/wouter/types/memory-location.d.ts
generated
vendored
Normal file
20
node_modules/wouter/types/memory-location.d.ts
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
import { BaseLocationHook, Path } from "./location-hook.js";
|
||||
|
||||
type Navigate<S = any> = (
|
||||
to: Path,
|
||||
options?: { replace?: boolean; state?: S }
|
||||
) => void;
|
||||
|
||||
type HookReturnValue = { hook: BaseLocationHook; navigate: Navigate };
|
||||
type StubHistory = { history: Path[]; reset: () => void };
|
||||
|
||||
export function memoryLocation(options?: {
|
||||
path?: Path;
|
||||
static?: boolean;
|
||||
record?: false;
|
||||
}): HookReturnValue;
|
||||
export function memoryLocation(options?: {
|
||||
path?: Path;
|
||||
static?: boolean;
|
||||
record: true;
|
||||
}): HookReturnValue & StubHistory;
|
||||
36
node_modules/wouter/types/router.d.ts
generated
vendored
Normal file
36
node_modules/wouter/types/router.d.ts
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
import {
|
||||
Path,
|
||||
SearchString,
|
||||
BaseLocationHook,
|
||||
BaseSearchHook,
|
||||
} from "./location-hook.js";
|
||||
|
||||
export type Parser = (
|
||||
route: Path,
|
||||
loose?: boolean
|
||||
) => { pattern: RegExp; keys: string[] };
|
||||
|
||||
export type HrefsFormatter = (href: string, router: RouterObject) => string;
|
||||
|
||||
// the object returned from `useRouter`
|
||||
export interface RouterObject {
|
||||
readonly hook: BaseLocationHook;
|
||||
readonly searchHook: BaseSearchHook;
|
||||
readonly base: Path;
|
||||
readonly ownBase: Path;
|
||||
readonly parser: Parser;
|
||||
readonly ssrPath?: Path;
|
||||
readonly ssrSearch?: SearchString;
|
||||
readonly hrefs: HrefsFormatter;
|
||||
}
|
||||
|
||||
// basic options to construct a router
|
||||
export type RouterOptions = {
|
||||
hook?: BaseLocationHook;
|
||||
searchHook?: BaseSearchHook;
|
||||
base?: Path;
|
||||
parser?: Parser;
|
||||
ssrPath?: Path;
|
||||
ssrSearch?: SearchString;
|
||||
hrefs?: HrefsFormatter;
|
||||
};
|
||||
35
node_modules/wouter/types/use-browser-location.d.ts
generated
vendored
Normal file
35
node_modules/wouter/types/use-browser-location.d.ts
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
import { Path, SearchString } from "./location-hook.js";
|
||||
|
||||
type Primitive = string | number | bigint | boolean | null | undefined | symbol;
|
||||
export const useLocationProperty: <S extends Primitive>(
|
||||
fn: () => S,
|
||||
ssrFn?: () => S
|
||||
) => S;
|
||||
|
||||
export type BrowserSearchHook = (options?: {
|
||||
ssrSearch?: SearchString;
|
||||
}) => SearchString;
|
||||
|
||||
export const useSearch: BrowserSearchHook;
|
||||
|
||||
export const usePathname: (options?: { ssrPath?: Path }) => Path;
|
||||
|
||||
export const useHistoryState: <T = any>() => T;
|
||||
|
||||
export const navigate: <S = any>(
|
||||
to: string | URL,
|
||||
options?: { replace?: boolean; state?: S }
|
||||
) => void;
|
||||
|
||||
/*
|
||||
* Default `useLocation`
|
||||
*/
|
||||
|
||||
// The type of the default `useLocation` hook that wouter uses.
|
||||
// It operates on current URL using History API, supports base path and can
|
||||
// navigate with `pushState` or `replaceState`.
|
||||
export type BrowserLocationHook = (options?: {
|
||||
ssrPath?: Path;
|
||||
}) => [Path, typeof navigate];
|
||||
|
||||
export const useBrowserLocation: BrowserLocationHook;
|
||||
7
node_modules/wouter/types/use-hash-location.d.ts
generated
vendored
Normal file
7
node_modules/wouter/types/use-hash-location.d.ts
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
import { Path } from "./location-hook.js";
|
||||
|
||||
export function navigate<S = any>(to: Path, options?: { state: S }): void;
|
||||
|
||||
export function useHashLocation(options?: {
|
||||
ssrPath?: Path;
|
||||
}): [Path, typeof navigate];
|
||||
Reference in New Issue
Block a user