@alxia/react-router 0.25.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1028,6 +1028,22 @@ export const action = events.action; // POST
1028
1028
  See the [subscription guide](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/subscriptions.md)
1029
1029
  for the rules, the options and the errors.
1030
1030
 
1031
+ ## A UI library's links through React Router
1032
+
1033
+ A design system that renders its links through a component of its own (a
1034
+ `LinkComponentProvider`) hands it an `href`, while React Router's `Link` takes
1035
+ `to`. `@alxia/react-router/link` (`react` and `react-router` are its only
1036
+ imports) exports the adapter, so no app copies it:
1037
+
1038
+ ```tsx
1039
+ // app/root.tsx
1040
+ import { RouterLink } from '@alxia/react-router/link';
1041
+
1042
+ <LinkComponentProvider component={RouterLink}>
1043
+ <Outlet />
1044
+ </LinkComponentProvider>
1045
+ ```
1046
+
1031
1047
  ## A list whose state is the URL
1032
1048
 
1033
1049
  `@alxia/react-router/lists` (`react` and `react-router` are its only imports; every hook renders on the server)
@@ -1306,6 +1322,12 @@ From `@alxia/react-router/lists` (`react` and `react-router` are its only import
1306
1322
  | `usePagedRows(firstPage, rowsOf, { cursorParam?, nextOf? }?)`, `PagedRows`, `PagedRowsOptions` | `{ rows, next, loading, error, loadMore }`: appends the pages the same route answers to `?<cursorParam>=<next>` (default `after`, `nextOf` default `page.next`) through `useFetcher`; starts over when `firstPage` is another object; a failure keeps the rows and sets `error` (an answer that is not a page), and data that is not a page is ignored |
1307
1323
  | `orFailed(after, run)`, `FAILED`, `isFailedPage(value)`, `FailedPage` | the loader half of `usePagedRows`: `run()`'s answer; an error is rethrown when `after` is empty, and with an `after` it answers `FAILED` (`{ failed: true }`, typed `Promise<T \| FailedPage>`), except a 3xx `Response`, which is rethrown. `isFailedPage` narrows an answer |
1308
1324
 
1325
+ From `@alxia/react-router/link` (`react` and `react-router` are its only imports; it renders on the server):
1326
+
1327
+ | export | |
1328
+ | --- | --- |
1329
+ | `RouterLink`, `RouterLinkProps` | React Router's `Link` as a UI library's link component: takes `href` (or `to`), spreads `className`, `children`, `ref` and every anchor prop onto the `<a>`, keeps `Link`'s own props (`replace`, `prefetch`, `reloadDocument`, ...) |
1330
+
1309
1331
  From `@alxia/react-router/subscription` (no import, no peer):
1310
1332
 
1311
1333
  | export | |
@@ -0,0 +1,7 @@
1
+ /**
2
+ * `@alxia/react-router/link`: React Router's `Link` as the link component of
3
+ * a UI library. `react` and `react-router` are its only imports, and it
4
+ * renders on the server.
5
+ */
6
+ export { RouterLink, type RouterLinkProps } from './router-link';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/link/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,16 @@
1
+ // src/link/router-link.ts
2
+ import { createElement } from "react";
3
+ import { Link } from "react-router";
4
+ function RouterLink({
5
+ href,
6
+ to,
7
+ ...props
8
+ }) {
9
+ return createElement(Link, { ...props, to: href ?? to });
10
+ }
11
+ export {
12
+ RouterLink
13
+ };
14
+
15
+ //# debugId=D9C7D4DF88CF5C0964756E2164756E21
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/link/router-link.ts"],
4
+ "sourcesContent": [
5
+ "import { createElement, type ReactElement, type RefAttributes } from 'react';\nimport { Link, type LinkProps, type To } from 'react-router';\n\n/**\n * The props of {@link RouterLink}: React Router's `Link` props, with the\n * destination given as `href` (what a design system's link component hands\n * over) or as `to` (React Router's own name), never both.\n */\nexport type RouterLinkProps = Omit<LinkProps, 'to'> &\n\tRefAttributes<HTMLAnchorElement> &\n\t({ href: To; to?: undefined } | { to: To; href?: undefined });\n\n/**\n * React Router's `Link` for a UI library that renders its links through a\n * component of its own (a `LinkComponentProvider`): it receives `href` and\n * spreads every other prop (`className`, `children`, `aria-*`, `ref`, ...)\n * onto the anchor. Navigation stays client-side and `<Link>`'s own props\n * (`prefetch`, `replace`, `reloadDocument`, ...) still apply.\n *\n * ```tsx\n * <LinkComponentProvider component={RouterLink}>...</LinkComponentProvider>\n * ```\n */\nexport function RouterLink({\n\thref,\n\tto,\n\t...props\n}: RouterLinkProps): ReactElement {\n\t// The union guarantees one of the two is set; TypeScript cannot see it\n\t// through the destructuring, hence the cast.\n\treturn createElement(Link, { ...props, to: (href ?? to) as To });\n}\n"
6
+ ],
7
+ "mappings": ";AAAA;AACA;AAsBO,SAAS,UAAU;AAAA,EACzB;AAAA,EACA;AAAA,KACG;AAAA,GAC8B;AAAA,EAGjC,OAAO,cAAc,MAAM,KAAK,OAAO,IAAK,QAAQ,GAAU,CAAC;AAAA;",
8
+ "debugId": "D9C7D4DF88CF5C0964756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,27 @@
1
+ import { type ReactElement, type RefAttributes } from 'react';
2
+ import { type LinkProps, type To } from 'react-router';
3
+ /**
4
+ * The props of {@link RouterLink}: React Router's `Link` props, with the
5
+ * destination given as `href` (what a design system's link component hands
6
+ * over) or as `to` (React Router's own name), never both.
7
+ */
8
+ export type RouterLinkProps = Omit<LinkProps, 'to'> & RefAttributes<HTMLAnchorElement> & ({
9
+ href: To;
10
+ to?: undefined;
11
+ } | {
12
+ to: To;
13
+ href?: undefined;
14
+ });
15
+ /**
16
+ * React Router's `Link` for a UI library that renders its links through a
17
+ * component of its own (a `LinkComponentProvider`): it receives `href` and
18
+ * spreads every other prop (`className`, `children`, `aria-*`, `ref`, ...)
19
+ * onto the anchor. Navigation stays client-side and `<Link>`'s own props
20
+ * (`prefetch`, `replace`, `reloadDocument`, ...) still apply.
21
+ *
22
+ * ```tsx
23
+ * <LinkComponentProvider component={RouterLink}>...</LinkComponentProvider>
24
+ * ```
25
+ */
26
+ export declare function RouterLink({ href, to, ...props }: RouterLinkProps): ReactElement;
27
+ //# sourceMappingURL=router-link.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router-link.d.ts","sourceRoot":"","sources":["../../src/link/router-link.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC;AAC7E,OAAO,EAAQ,KAAK,SAAS,EAAE,KAAK,EAAE,EAAE,MAAM,cAAc,CAAC;AAE7D;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,GAClD,aAAa,CAAC,iBAAiB,CAAC,GAChC,CAAC;IAAE,IAAI,EAAE,EAAE,CAAC;IAAC,EAAE,CAAC,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,EAAE,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,CAAA;CAAE,CAAC,CAAC;AAE/D;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,EAC1B,IAAI,EACJ,EAAE,EACF,GAAG,KAAK,EACR,EAAE,eAAe,GAAG,YAAY,CAIhC"}
package/docs/README.md CHANGED
@@ -20,6 +20,7 @@ installable and usable offline, wording an API's refusals, the BFF session, a Gr
20
20
  | [Installable and offline](guide/pwa.md) | making the app installable and usable offline: the subpaths of `@alxia/react-router/pwa`, the service worker (`startWorker`, `/pwa/worker`), its build by `alxia()` or `buildWorker` (`/pwa/build`), its contract and cache names, which requests the worker caches and which responses it stores, the precache list read from a build, or clearing the caches at sign-out |
21
21
  | [An API's refusals](guide/errors.md) | wording a GraphQL API's refusals for the pages: recognising an `ApiError`, the request id, the page a request is for, the catalogue store, the form checks in the API's words, the provider, and the loaders, the error page and the actions that answer with it, the JSON actions with their CSRF check and fetcher hook, calling the API from a loader (`apiCallFailure`), and keeping the catalogue fresh (`@alxia/react-router/errors`) |
22
22
  | [A BFF session](guide/session.md) | redirecting after sign-in to a page of the site and nowhere else, the sign-in pages' paths, the session's cookies, the sign-in flow (where an answer lands, a refused code, the viewer, sign-out; `@alxia/react-router/session`), the pages a mail's link opens (a magic link, a password reset: `authLinks`), the page's hooks (sign-out, a mail link's parameter, a one-code field; `@alxia/react-router/session/react`), and the step-up's state and the props it gives the UI (`@alxia/react-router/session/model`) |
23
+ | [Links through React Router](guide/link.md) | `RouterLink`, React Router's `Link` as the link component of a UI library that hands `href` and spreads anchor props; `@alxia/react-router/link` |
23
24
  | [Lists whose state is the URL](guide/lists.md) | a search and filters in the address bar that drop the cursor (`useSearchChange`), a skeleton while the same page reloads (`useSameRouteLoading`), and a load-more button that appends pages by cursor and keeps the rows when one fails (`usePagedRows`); `@alxia/react-router/lists` |
24
25
  | [Subscriptions for a BFF](guide/subscriptions.md) | showing a GraphQL subscription in the browser without giving it the API's token or a way to write a document: a resource route with a map of operation names to the server's documents, the same-site check, the bearer from the session, extra upstream headers, a heartbeat, abort propagation, size limits and a 429 passed through with `retryAfter` (`@alxia/react-router/subscription`) |
25
26
  | [Troubleshooting](troubleshooting.md) | something went wrong and you have the message, or a page streams, hydrates or reads its context unlike you expected |
@@ -0,0 +1,32 @@
1
+ # Links through React Router
2
+
3
+ A UI library that renders its links through a component of its own (a
4
+ `LinkComponentProvider`) hands that component an `href` and spreads every other
5
+ prop onto the anchor. React Router's `Link` takes `to`, so an app adapts it.
6
+ `@alxia/react-router/link` exports that adapter. `react` and `react-router` are
7
+ its only imports, and it renders on the server.
8
+
9
+ ```tsx
10
+ // app/root.tsx
11
+ import { RouterLink } from '@alxia/react-router/link';
12
+
13
+ export default function App() {
14
+ return (
15
+ <LinkComponentProvider component={RouterLink}>
16
+ <Outlet />
17
+ </LinkComponentProvider>
18
+ );
19
+ }
20
+ ```
21
+
22
+ ## What it does
23
+
24
+ - `href` (or `to`, React Router's own name, never both) is the destination: a
25
+ string or a `To` object.
26
+ - An external target (`https://…`, `mailto:`, `//host`) renders a plain anchor with that `href`; React Router's `Link` already handles it.
27
+ - `className`, `children`, `ref`, `aria-*`, `target` and every other anchor prop
28
+ reach the `<a>`.
29
+ - `Link`'s own props (`replace`, `prefetch`, `reloadDocument`, `state`, ...) still
30
+ apply, so a link can opt out of client-side navigation.
31
+
32
+ `RouterLinkProps` is the type of its props, for a wrapper of your own.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alxia/react-router",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "description": "alxia as the server of a React Router framework app: server rendering behind the app's hooks, loaders reading its typed context, the client's assets served; the code with no UI an installable app shares (`/pwa`), its hooks (`/pwa/react`), `noCache` for the worker's script (`/pwa/server`), the service worker on Workbox (`/pwa/worker`) and its build (`/pwa/build`), an API's refusals worded for the page (`/errors`), the BFF session: cookies, sign-in, step-up and mail links (`/session`), its page hooks (`/session/react`) and what it shares with the page (`/session/model`), a GraphQL subscription relayed to the browser over SSE (`/subscription`), the page's CSP nonce and inline boot scripts (`/document`) and a streaming `entry.server` (`/entry`)",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -87,6 +87,11 @@
87
87
  "import": "./dist/lists/index.js",
88
88
  "default": "./dist/lists/index.js"
89
89
  },
90
+ "./link": {
91
+ "types": "./dist/link/index.d.ts",
92
+ "import": "./dist/link/index.js",
93
+ "default": "./dist/link/index.js"
94
+ },
90
95
  "./subscription": {
91
96
  "types": "./dist/subscription/index.d.ts",
92
97
  "import": "./dist/subscription/index.js",
@@ -121,6 +126,7 @@
121
126
  "src/session/react/index.ts",
122
127
  "src/subscription/index.ts",
123
128
  "src/lists/index.ts",
129
+ "src/link/index.ts",
124
130
  "src/document/index.ts",
125
131
  "src/entry/index.ts",
126
132
  "src/pwa/worker/index.ts",