@solidjs/router 0.16.0 → 0.17.0-next.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 +85 -79
- package/dist/components.jsx +22 -16
- package/dist/data/action.d.ts +12 -10
- package/dist/data/action.js +98 -78
- package/dist/data/events.d.ts +8 -1
- package/dist/data/events.js +3 -3
- package/dist/data/index.d.ts +2 -3
- package/dist/data/index.js +2 -3
- package/dist/data/query.d.ts +1 -3
- package/dist/data/query.js +10 -16
- package/dist/index.d.ts +2 -2
- package/dist/index.js +212 -261
- package/dist/index.jsx +1 -1
- package/dist/lifecycle.js +1 -1
- package/dist/routers/HashRouter.js +1 -1
- package/dist/routers/MemoryRouter.js +1 -1
- package/dist/routers/Router.js +2 -2
- package/dist/routers/StaticRouter.js +1 -1
- package/dist/routers/components.d.ts +0 -4
- package/dist/routers/components.jsx +30 -19
- package/dist/routing.d.ts +4 -3
- package/dist/routing.js +71 -49
- package/dist/types.d.ts +3 -18
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +8 -0
- package/package.json +8 -6
- package/dist/data/createAsync.d.ts +0 -32
- package/dist/data/createAsync.js +0 -93
- package/dist/src/components.d.ts +0 -31
- package/dist/src/components.jsx +0 -39
- package/dist/src/data/action.d.ts +0 -17
- package/dist/src/data/action.js +0 -163
- package/dist/src/data/action.spec.d.ts +0 -1
- package/dist/src/data/action.spec.js +0 -297
- package/dist/src/data/createAsync.d.ts +0 -32
- package/dist/src/data/createAsync.js +0 -96
- package/dist/src/data/createAsync.spec.d.ts +0 -1
- package/dist/src/data/createAsync.spec.js +0 -196
- package/dist/src/data/events.d.ts +0 -9
- package/dist/src/data/events.js +0 -123
- package/dist/src/data/events.spec.d.ts +0 -1
- package/dist/src/data/events.spec.js +0 -567
- package/dist/src/data/index.d.ts +0 -4
- package/dist/src/data/index.js +0 -4
- package/dist/src/data/query.d.ts +0 -23
- package/dist/src/data/query.js +0 -232
- package/dist/src/data/query.spec.d.ts +0 -1
- package/dist/src/data/query.spec.js +0 -354
- package/dist/src/data/response.d.ts +0 -4
- package/dist/src/data/response.js +0 -42
- package/dist/src/data/response.spec.d.ts +0 -1
- package/dist/src/data/response.spec.js +0 -165
- package/dist/src/index.d.ts +0 -7
- package/dist/src/index.jsx +0 -6
- package/dist/src/lifecycle.d.ts +0 -5
- package/dist/src/lifecycle.js +0 -69
- package/dist/src/routers/HashRouter.d.ts +0 -9
- package/dist/src/routers/HashRouter.js +0 -41
- package/dist/src/routers/MemoryRouter.d.ts +0 -24
- package/dist/src/routers/MemoryRouter.js +0 -57
- package/dist/src/routers/Router.d.ts +0 -9
- package/dist/src/routers/Router.js +0 -45
- package/dist/src/routers/StaticRouter.d.ts +0 -6
- package/dist/src/routers/StaticRouter.js +0 -15
- package/dist/src/routers/components.d.ts +0 -27
- package/dist/src/routers/components.jsx +0 -118
- package/dist/src/routers/createRouter.d.ts +0 -10
- package/dist/src/routers/createRouter.js +0 -41
- package/dist/src/routers/index.d.ts +0 -11
- package/dist/src/routers/index.js +0 -6
- package/dist/src/routing.d.ts +0 -175
- package/dist/src/routing.js +0 -560
- package/dist/src/types.d.ts +0 -200
- package/dist/src/types.js +0 -1
- package/dist/src/utils.d.ts +0 -13
- package/dist/src/utils.js +0 -185
- package/dist/test/helpers.d.ts +0 -6
- package/dist/test/helpers.js +0 -50
package/README.md
CHANGED
|
@@ -64,7 +64,7 @@ npm add @solidjs/router
|
|
|
64
64
|
Install `@solidjs/router`, then start your application by rendering the router component
|
|
65
65
|
|
|
66
66
|
```jsx
|
|
67
|
-
import { render } from "
|
|
67
|
+
import { render } from "@solidjs/web";
|
|
68
68
|
import { Router } from "@solidjs/router";
|
|
69
69
|
|
|
70
70
|
render(() => <Router />, document.getElementById("app"));
|
|
@@ -79,7 +79,7 @@ Solid Router allows you to configure your routes using JSX:
|
|
|
79
79
|
1. Add each route to a `<Router>` using the `Route` component, specifying a path and a component to render when the user navigates to that path.
|
|
80
80
|
|
|
81
81
|
```jsx
|
|
82
|
-
import { render } from "
|
|
82
|
+
import { render } from "@solidjs/web";
|
|
83
83
|
import { Router, Route } from "@solidjs/router";
|
|
84
84
|
|
|
85
85
|
import Home from "./pages/Home";
|
|
@@ -101,7 +101,7 @@ render(
|
|
|
101
101
|
This will always be there and won't update on page change. It is the ideal place to put top level navigation and Context Providers
|
|
102
102
|
|
|
103
103
|
```jsx
|
|
104
|
-
import { render } from "
|
|
104
|
+
import { render } from "@solidjs/web";
|
|
105
105
|
import { Router, Route } from "@solidjs/router";
|
|
106
106
|
|
|
107
107
|
import Home from "./pages/Home";
|
|
@@ -130,7 +130,7 @@ render(
|
|
|
130
130
|
We can create catch-all routes for pages not found at any nested level of the router. We use `*` and optionally the name of a parameter to retrieve the rest of the path.
|
|
131
131
|
|
|
132
132
|
```jsx
|
|
133
|
-
import { render } from "
|
|
133
|
+
import { render } from "@solidjs/web";
|
|
134
134
|
import { Router, Route } from "@solidjs/router";
|
|
135
135
|
|
|
136
136
|
import Home from "./pages/Home";
|
|
@@ -162,7 +162,7 @@ This way, the `Users` and `Home` components will only be loaded if you're naviga
|
|
|
162
162
|
|
|
163
163
|
```jsx
|
|
164
164
|
import { lazy } from "solid-js";
|
|
165
|
-
import { render } from "
|
|
165
|
+
import { render } from "@solidjs/web";
|
|
166
166
|
import { Router, Route } from "@solidjs/router";
|
|
167
167
|
|
|
168
168
|
const Users = lazy(() => import("./pages/Users"));
|
|
@@ -192,7 +192,7 @@ Use an anchor tag that takes you to a route:
|
|
|
192
192
|
|
|
193
193
|
```jsx
|
|
194
194
|
import { lazy } from "solid-js";
|
|
195
|
-
import { render } from "
|
|
195
|
+
import { render } from "@solidjs/web";
|
|
196
196
|
import { Router, Route } from "@solidjs/router";
|
|
197
197
|
|
|
198
198
|
const Users = lazy(() => import("./pages/Users"));
|
|
@@ -226,7 +226,7 @@ If you don't know the path ahead of time, you might want to treat part of the pa
|
|
|
226
226
|
|
|
227
227
|
```jsx
|
|
228
228
|
import { lazy } from "solid-js";
|
|
229
|
-
import { render } from "
|
|
229
|
+
import { render } from "@solidjs/web";
|
|
230
230
|
import { Router, Route } from "@solidjs/router";
|
|
231
231
|
|
|
232
232
|
const Users = lazy(() => import("./pages/Users"));
|
|
@@ -265,7 +265,7 @@ This allows for more complex routing descriptions than just checking the presenc
|
|
|
265
265
|
|
|
266
266
|
```jsx
|
|
267
267
|
import { lazy } from "solid-js";
|
|
268
|
-
import { render } from "
|
|
268
|
+
import { render } from "@solidjs/web";
|
|
269
269
|
import { Router, Route } from "@solidjs/router";
|
|
270
270
|
import type { MatchFilters } from "@solidjs/router";
|
|
271
271
|
|
|
@@ -498,9 +498,10 @@ Inside your page component you:
|
|
|
498
498
|
```jsx
|
|
499
499
|
// pages/users/[id].js
|
|
500
500
|
import { getUser } from ... // the query function
|
|
501
|
+
import { createMemo } from "solid-js";
|
|
501
502
|
|
|
502
503
|
export default function User(props) {
|
|
503
|
-
const user =
|
|
504
|
+
const user = createMemo(() => getUser(props.params.id));
|
|
504
505
|
return <h1>{user().name}</h1>;
|
|
505
506
|
}
|
|
506
507
|
```
|
|
@@ -518,35 +519,24 @@ You can revalidate the query using the `revalidate` method or you can set `reval
|
|
|
518
519
|
|
|
519
520
|
`query` can be defined anywhere and then used inside your components with:
|
|
520
521
|
|
|
521
|
-
###
|
|
522
|
+
### Async reads in Solid 2
|
|
522
523
|
|
|
523
|
-
|
|
524
|
+
On this Solid 2 branch, `query()` results are meant to be consumed directly with Solid primitives like `createMemo` and `createProjection`.
|
|
524
525
|
|
|
525
526
|
```jsx
|
|
526
|
-
const user =
|
|
527
|
+
const user = createMemo(() => getUser(params.id));
|
|
528
|
+
return <h1>{user().name}</h1>;
|
|
527
529
|
```
|
|
528
530
|
|
|
529
|
-
|
|
531
|
+
For object-shaped data where you want a deeply reactive result, use `createProjection`.
|
|
530
532
|
|
|
531
533
|
```jsx
|
|
532
|
-
const
|
|
533
|
-
return <h1>{user.latest.name}</h1>;
|
|
534
|
-
```
|
|
535
|
-
|
|
536
|
-
Using `query` in `createResource` directly won't work properly as the fetcher is not reactive and it won't invalidate properly.
|
|
537
|
-
|
|
538
|
-
### `createAsyncStore`
|
|
539
|
-
|
|
540
|
-
Similar to `createAsync` except it uses a deeply reactive store. Perfect for applying fine-grained changes to large model data that updates.
|
|
541
|
-
It also supports `latest` field which will be removed in the future.
|
|
542
|
-
|
|
543
|
-
```jsx
|
|
544
|
-
const todos = createAsyncStore(() => getTodos());
|
|
534
|
+
const todos = createProjection(() => getTodos(), []);
|
|
545
535
|
```
|
|
546
536
|
|
|
547
537
|
### `action`
|
|
548
538
|
|
|
549
|
-
|
|
539
|
+
Router `action()` is the router-aware mutation wrapper for Solid 2. It keeps form submission, redirects, and invalidation wired into the router while letting you compose optimistic UI with Solid's built-in primitives.
|
|
550
540
|
|
|
551
541
|
```jsx
|
|
552
542
|
import { action, revalidate, redirect } from "@solidjs/router"
|
|
@@ -566,6 +556,29 @@ const myAction = action(async (data) => {
|
|
|
566
556
|
|
|
567
557
|
Actions only work with post requests, so make sure to put `method="post"` on your form.
|
|
568
558
|
|
|
559
|
+
For optimistic updates, use Solid's optimistic primitives for the rendered state and attach owner-scoped submit hooks to the router action:
|
|
560
|
+
|
|
561
|
+
```jsx
|
|
562
|
+
import { createOptimisticStore } from "solid-js";
|
|
563
|
+
import { action, query } from "@solidjs/router";
|
|
564
|
+
|
|
565
|
+
const getTodos = query(async () => fetchTodos(), "todos");
|
|
566
|
+
const [todos, setTodos] = createOptimisticStore(() => getTodos(), []);
|
|
567
|
+
|
|
568
|
+
const addTodo = action(async (todo) => {
|
|
569
|
+
await saveTodo(todo);
|
|
570
|
+
return { ok: true, todo };
|
|
571
|
+
}, "add-todo").onSubmit(todo => {
|
|
572
|
+
setTodos(items => {
|
|
573
|
+
items.push({ ...todo, pending: true });
|
|
574
|
+
});
|
|
575
|
+
});
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
`myAction.onSubmit(...)` registers a listener for that action in the current reactive owner. Multiple components can register hooks against the same action, and those hooks are automatically removed when their owner is disposed. `myAction.onSettled(...)` works the same way for observing completed submissions.
|
|
579
|
+
|
|
580
|
+
The preferred pattern is for actions to return values and let the client interpret the result. Throwing errors is still supported, but `Submission.error` is mainly an escape hatch for that legacy style.
|
|
581
|
+
|
|
569
582
|
Sometimes it might be easier to deal with typed data instead of `FormData` and adding additional hidden fields. For that reason Actions have a with method. That works similar to `bind` which applies the arguments in order.
|
|
570
583
|
|
|
571
584
|
Picture an action that deletes Todo Item:
|
|
@@ -592,7 +605,7 @@ const deleteTodo = action(api.deleteTodo)
|
|
|
592
605
|
</form>
|
|
593
606
|
```
|
|
594
607
|
|
|
595
|
-
Actions also take a second argument which can be the name or an option object with `name
|
|
608
|
+
Actions also take a second argument which can be the name or an option object with `name`. `name` is used to identify SSR actions that aren't server functions (see note below).
|
|
596
609
|
|
|
597
610
|
#### Notes on `<form>` implementation and SSR
|
|
598
611
|
|
|
@@ -614,24 +627,26 @@ submit(...args);
|
|
|
614
627
|
|
|
615
628
|
The outside of a form context you can use custom data instead of formData, and these helpers preserve types. However, even when used with server functions (in projects like SolidStart) this requires client side javascript and is not Progressive Enhanceable like forms are.
|
|
616
629
|
|
|
617
|
-
### `
|
|
630
|
+
### `useSubmissions`
|
|
618
631
|
|
|
619
|
-
|
|
632
|
+
This returns settled submission records for an action. It is useful for reading completed results, clearing old submissions, retrying a prior submission, or replaying settled errors. It is not the optimistic state layer.
|
|
620
633
|
|
|
621
634
|
```jsx
|
|
622
635
|
type Submission<T, U> = {
|
|
623
636
|
readonly input: T;
|
|
624
637
|
readonly result?: U;
|
|
625
|
-
readonly
|
|
638
|
+
readonly error: any;
|
|
626
639
|
readonly url: string;
|
|
627
640
|
clear: () => void;
|
|
628
641
|
retry: () => void;
|
|
629
642
|
};
|
|
630
643
|
|
|
631
644
|
const submissions = useSubmissions(action, (input) => filter(input));
|
|
632
|
-
const
|
|
645
|
+
const latestSubmission = submissions.at(-1);
|
|
633
646
|
```
|
|
634
647
|
|
|
648
|
+
Use Solid's `createOptimistic` or `createOptimisticStore` for in-flight UI, and use submissions as the durable settled record layer.
|
|
649
|
+
|
|
635
650
|
### Response Helpers
|
|
636
651
|
|
|
637
652
|
These are used to communicate router navigations from query/actions, and can include invalidation hints. Generally these are thrown to not interfere the with the types and make it clear that function ends execution at that point.
|
|
@@ -670,7 +685,7 @@ You don't have to use JSX to set up your routes; you can pass an array of route
|
|
|
670
685
|
|
|
671
686
|
```jsx
|
|
672
687
|
import { lazy } from "solid-js";
|
|
673
|
-
import { render } from "
|
|
688
|
+
import { render } from "@solidjs/web";
|
|
674
689
|
import { Router } from "@solidjs/router";
|
|
675
690
|
|
|
676
691
|
const routes = [
|
|
@@ -713,7 +728,7 @@ Also you can pass a single route definition object for a single route:
|
|
|
713
728
|
|
|
714
729
|
```jsx
|
|
715
730
|
import { lazy } from "solid-js";
|
|
716
|
-
import { render } from "
|
|
731
|
+
import { render } from "@solidjs/web";
|
|
717
732
|
import { Router } from "@solidjs/router";
|
|
718
733
|
|
|
719
734
|
const route = {
|
|
@@ -751,7 +766,7 @@ import { MemoryRouter } from "@solidjs/router";
|
|
|
751
766
|
For SSR you can use the static router directly or the browser Router defaults to it on the server, just pass in the url.
|
|
752
767
|
|
|
753
768
|
```jsx
|
|
754
|
-
import { isServer } from "
|
|
769
|
+
import { isServer } from "@solidjs/web";
|
|
755
770
|
import { Router } from "@solidjs/router";
|
|
756
771
|
|
|
757
772
|
<Router url={isServer ? req.url : ""} />;
|
|
@@ -884,13 +899,13 @@ return (
|
|
|
884
899
|
|
|
885
900
|
### useIsRouting
|
|
886
901
|
|
|
887
|
-
Retrieves signal that indicates whether the
|
|
902
|
+
Retrieves a signal that indicates whether the router is currently processing a navigation. Useful for showing pending navigation state while the next route and its data settle.
|
|
888
903
|
|
|
889
904
|
```js
|
|
890
905
|
const isRouting = useIsRouting();
|
|
891
906
|
|
|
892
907
|
return (
|
|
893
|
-
<div
|
|
908
|
+
<div class={{ "grey-out": isRouting() }}>
|
|
894
909
|
<MyAwesomeContent />
|
|
895
910
|
</div>
|
|
896
911
|
);
|
|
@@ -903,7 +918,7 @@ return (
|
|
|
903
918
|
```js
|
|
904
919
|
const match = useMatch(() => props.href);
|
|
905
920
|
|
|
906
|
-
return <div
|
|
921
|
+
return <div class={{ active: Boolean(match()) }} />;
|
|
907
922
|
```
|
|
908
923
|
|
|
909
924
|
### useCurrentMatches
|
|
@@ -958,60 +973,51 @@ useBeforeLeave((e: BeforeLeaveEventArgs) => {
|
|
|
958
973
|
});
|
|
959
974
|
```
|
|
960
975
|
|
|
961
|
-
##
|
|
962
|
-
|
|
963
|
-
v0.10.0 brings some big changes to support the future of routing including Islands/Partial Hydration hybrid solutions. Most notably there is no Context API available in non-hydrating parts of the application.
|
|
964
|
-
|
|
965
|
-
The biggest changes are around removed APIs that need to be replaced.
|
|
966
|
-
|
|
967
|
-
### `<Outlet>`, `<Routes>`, `useRoutes`
|
|
968
|
-
|
|
969
|
-
This is no longer used and instead will use `props.children` passed from into the page components for outlets. This keeps the outlet directly passed from its page and avoids oddness of trying to use context across Islands boundaries. Nested `<Routes>` components inherently cause waterfalls and are `<Outlets>` themselves so they have the same concerns.
|
|
970
|
-
|
|
971
|
-
Keep in mind no `<Routes>` means the `<Router>` API is different. The `<Router>` acts as the `<Routes>` component and its children can only be `<Route>` components. Your top-level layout should go in the root prop of the router [as shown above](#configure-your-routes)
|
|
976
|
+
## Migration from 0.16.x
|
|
972
977
|
|
|
973
|
-
|
|
978
|
+
This branch is the Solid 2 migration. Most route configuration stays the same, but the data APIs and recommended async patterns have changed.
|
|
974
979
|
|
|
975
|
-
|
|
980
|
+
### Async reads move to Solid 2 primitives
|
|
976
981
|
|
|
977
|
-
|
|
982
|
+
`createAsync` and `createAsyncStore` are gone. Read query results with Solid 2 primitives like `createMemo`, `createProjection`, `createOptimistic`, and `createOptimisticStore`.
|
|
978
983
|
|
|
979
|
-
|
|
984
|
+
```jsx
|
|
985
|
+
const user = createMemo(() => getUser(params.id));
|
|
986
|
+
const [todos, setTodos] = createOptimisticStore(() => getTodos(), []);
|
|
987
|
+
```
|
|
980
988
|
|
|
981
|
-
|
|
989
|
+
### `query()` stays the source of truth
|
|
982
990
|
|
|
983
|
-
|
|
984
|
-
import { lazy } from "solid-js";
|
|
985
|
-
import { Route } from "@solidjs/router";
|
|
991
|
+
Continue using `query()` for cached reads and invalidation, but consume the results directly through Solid 2's async primitives instead of router-specific wrappers.
|
|
986
992
|
|
|
987
|
-
|
|
993
|
+
### `action()` lifecycle hooks changed
|
|
988
994
|
|
|
989
|
-
|
|
990
|
-
function preloadUser({ params, location }) {
|
|
991
|
-
const [user] = createResource(() => params.id, fetchUser);
|
|
992
|
-
return user;
|
|
993
|
-
}
|
|
995
|
+
The action API is now centered around instance methods:
|
|
994
996
|
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
997
|
+
```jsx
|
|
998
|
+
const saveTodo = action(async (todo) => {
|
|
999
|
+
await api.saveTodo(todo);
|
|
1000
|
+
return { ok: true, todo };
|
|
1001
|
+
}, "save-todo")
|
|
1002
|
+
.onSubmit(todo => {
|
|
1003
|
+
// optimistic write
|
|
1004
|
+
})
|
|
1005
|
+
.onSettled(submission => {
|
|
1006
|
+
// observe settled result or retry state
|
|
1007
|
+
});
|
|
999
1008
|
```
|
|
1000
1009
|
|
|
1001
|
-
|
|
1010
|
+
- Use `onSubmit(...)` for owner-scoped optimistic/pre-submit work.
|
|
1011
|
+
- Use `onSettled(...)` for owner-scoped observation of completed submissions.
|
|
1012
|
+
- Use returned values for expected application-level results. Thrown errors are still captured on `Submission.error` when something fails unexpectedly.
|
|
1002
1013
|
|
|
1003
|
-
|
|
1004
|
-
function User(props) {
|
|
1005
|
-
<UserContext.Provider value={props.data}>
|
|
1006
|
-
{/* my component content */}
|
|
1007
|
-
</UserContext.Provider>;
|
|
1008
|
-
}
|
|
1014
|
+
### `useSubmissions()` is the submission API
|
|
1009
1015
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1016
|
+
Submissions are now settled history, not in-flight mutation state. Read them through `useSubmissions()` and select the latest entry with `at(-1)` when needed.
|
|
1017
|
+
|
|
1018
|
+
```jsx
|
|
1019
|
+
const submissions = useSubmissions(saveTodo);
|
|
1020
|
+
const latestSubmission = submissions.at(-1);
|
|
1015
1021
|
```
|
|
1016
1022
|
|
|
1017
1023
|
## SPAs in Deployed Environments
|
package/dist/components.jsx
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
|
-
import { createMemo,
|
|
1
|
+
import { createMemo, merge, omit } from "solid-js";
|
|
2
2
|
import { useHref, useLocation, useNavigate, useResolvedPath } from "./routing.js";
|
|
3
3
|
import { normalizePath } from "./utils.js";
|
|
4
|
+
function toClassName(value) {
|
|
5
|
+
if (!value)
|
|
6
|
+
return "";
|
|
7
|
+
if (typeof value === "string" || typeof value === "number")
|
|
8
|
+
return String(value);
|
|
9
|
+
if (Array.isArray(value))
|
|
10
|
+
return value.map(toClassName).filter(Boolean).join(" ");
|
|
11
|
+
if (typeof value === "object") {
|
|
12
|
+
return Object.entries(value)
|
|
13
|
+
.filter(([, enabled]) => enabled)
|
|
14
|
+
.map(([name]) => name)
|
|
15
|
+
.join(" ");
|
|
16
|
+
}
|
|
17
|
+
return "";
|
|
18
|
+
}
|
|
4
19
|
export function A(props) {
|
|
5
|
-
props =
|
|
6
|
-
const
|
|
7
|
-
"href",
|
|
8
|
-
"state",
|
|
9
|
-
"class",
|
|
10
|
-
"activeClass",
|
|
11
|
-
"inactiveClass",
|
|
12
|
-
"end"
|
|
13
|
-
]);
|
|
20
|
+
props = merge({ inactiveClass: "inactive", activeClass: "active" }, props);
|
|
21
|
+
const rest = omit(props, "href", "state", "class", "activeClass", "inactiveClass", "end");
|
|
14
22
|
const to = useResolvedPath(() => props.href);
|
|
15
23
|
const href = useHref(to);
|
|
16
24
|
const location = useLocation();
|
|
@@ -22,12 +30,10 @@ export function A(props) {
|
|
|
22
30
|
const loc = decodeURI(normalizePath(location.pathname).toLowerCase());
|
|
23
31
|
return [props.end ? path === loc : loc.startsWith(path + "/") || loc === path, path === loc];
|
|
24
32
|
});
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
...rest.classList
|
|
30
|
-
}} link aria-current={isActive()[1] ? "page" : undefined}/>);
|
|
33
|
+
const className = createMemo(() => [toClassName(props.class), isActive()[0] ? props.activeClass : props.inactiveClass]
|
|
34
|
+
.filter(Boolean)
|
|
35
|
+
.join(" "));
|
|
36
|
+
return (<a {...rest} href={href() || props.href} state={JSON.stringify(props.state)} class={className()} link aria-current={isActive()[1] ? "page" : undefined}/>);
|
|
31
37
|
}
|
|
32
38
|
export function Navigate(props) {
|
|
33
39
|
const navigate = useNavigate();
|
package/dist/data/action.d.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { JSX } from "solid-js";
|
|
2
|
-
import type { Submission,
|
|
2
|
+
import type { Submission, NarrowResponse } from "../types.js";
|
|
3
3
|
export type Action<T extends Array<any>, U, V = T> = (T extends [FormData | URLSearchParams] | [] ? JSX.SerializableAttributeValue : unknown) & ((...vars: T) => Promise<NarrowResponse<U>>) & {
|
|
4
4
|
url: string;
|
|
5
5
|
with<A extends any[], B extends any[]>(this: (this: any, ...args: [...A, ...B]) => Promise<NarrowResponse<U>>, ...args: A): Action<B, U, V>;
|
|
6
|
+
onSubmit(hook: (...args: V extends Array<any> ? V : T) => void): Action<T, U, V>;
|
|
7
|
+
onSettled(hook: (submission: Submission<V extends Array<any> ? V : T, NarrowResponse<U>>) => void): Action<T, U, V>;
|
|
6
8
|
};
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
type ActionFactory = {
|
|
10
|
+
<T extends Array<any>, U = void>(fn: (...args: T) => Promise<U>, name?: string): Action<T, U>;
|
|
11
|
+
<T extends Array<any>, U = void>(fn: (...args: T) => Promise<U>, options?: {
|
|
12
|
+
name?: string;
|
|
13
|
+
}): Action<T, U>;
|
|
10
14
|
};
|
|
11
|
-
export declare
|
|
15
|
+
export declare const actions: Map<string, Action<any, any, any>>;
|
|
16
|
+
export declare function useSubmissions<T extends Array<any>, U, V>(fn: Action<T, U, V>, filter?: (input: V) => boolean): Submission<V, NarrowResponse<U>>[];
|
|
12
17
|
export declare function useAction<T extends Array<any>, U, V>(action: Action<T, U, V>): (...args: Parameters<Action<T, U, V>>) => Promise<NarrowResponse<U>>;
|
|
13
|
-
export declare
|
|
14
|
-
export
|
|
15
|
-
name?: string;
|
|
16
|
-
onComplete?: (s: Submission<T, U>) => void;
|
|
17
|
-
}): Action<T, U>;
|
|
18
|
+
export declare const action: ActionFactory;
|
|
19
|
+
export {};
|
package/dist/data/action.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { $TRACK,
|
|
2
|
-
import { isServer } from "
|
|
1
|
+
import { $TRACK, action as createSolidAction, createMemo, onCleanup, getOwner } from "solid-js";
|
|
2
|
+
import { isServer } from "@solidjs/web";
|
|
3
3
|
import { useRouter } from "../routing.js";
|
|
4
|
-
import { mockBase } from "../utils.js";
|
|
4
|
+
import { mockBase, setFunctionName } from "../utils.js";
|
|
5
5
|
import { cacheKeyOp, hashKey, revalidate, query } from "./query.js";
|
|
6
|
+
const submitHooksSymbol = Symbol("routerActionSubmitHooks");
|
|
7
|
+
const settledHooksSymbol = Symbol("routerActionSettledHooks");
|
|
8
|
+
const invokeSymbol = Symbol("routerActionInvoke");
|
|
6
9
|
export const actions = /* #__PURE__ */ new Map();
|
|
7
10
|
export function useSubmissions(fn, filter) {
|
|
8
11
|
const router = useRouter();
|
|
@@ -11,8 +14,6 @@ export function useSubmissions(fn, filter) {
|
|
|
11
14
|
get(_, property) {
|
|
12
15
|
if (property === $TRACK)
|
|
13
16
|
return subs();
|
|
14
|
-
if (property === "pending")
|
|
15
|
-
return subs().some(sub => !sub.result);
|
|
16
17
|
return subs()[property];
|
|
17
18
|
},
|
|
18
19
|
has(_, property) {
|
|
@@ -20,101 +21,105 @@ export function useSubmissions(fn, filter) {
|
|
|
20
21
|
}
|
|
21
22
|
});
|
|
22
23
|
}
|
|
23
|
-
export function useSubmission(fn, filter) {
|
|
24
|
-
const submissions = useSubmissions(fn, filter);
|
|
25
|
-
return new Proxy({}, {
|
|
26
|
-
get(_, property) {
|
|
27
|
-
if ((submissions.length === 0 && property === "clear") || property === "retry")
|
|
28
|
-
return () => { };
|
|
29
|
-
return submissions[submissions.length - 1]?.[property];
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
24
|
export function useAction(action) {
|
|
34
25
|
const r = useRouter();
|
|
35
26
|
return (...args) => action.apply({ r }, args);
|
|
36
27
|
}
|
|
37
|
-
|
|
38
|
-
function
|
|
28
|
+
function actionImpl(fn, options = {}) {
|
|
29
|
+
async function invoke(variables, current) {
|
|
39
30
|
const router = this.r;
|
|
40
31
|
const form = this.f;
|
|
41
|
-
const
|
|
32
|
+
const submitHooks = current[submitHooksSymbol];
|
|
33
|
+
const settledHooks = current[settledHooksSymbol];
|
|
34
|
+
const runMutation = () => (router.singleFlight && fn.withOptions
|
|
42
35
|
? fn.withOptions({ headers: { "X-Single-Flight": "true" } })
|
|
43
36
|
: fn)(...variables);
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return submission.clear();
|
|
63
|
-
setResult(result);
|
|
64
|
-
if (result.error && !form)
|
|
65
|
-
throw result.error;
|
|
66
|
-
return result.data;
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
router.submissions[1](s => [
|
|
70
|
-
...s,
|
|
71
|
-
(submission = {
|
|
72
|
-
input: variables,
|
|
73
|
-
url,
|
|
74
|
-
get result() {
|
|
75
|
-
return result()?.data;
|
|
76
|
-
},
|
|
77
|
-
get error() {
|
|
78
|
-
return result()?.error;
|
|
79
|
-
},
|
|
80
|
-
get pending() {
|
|
81
|
-
return !result();
|
|
82
|
-
},
|
|
83
|
-
clear() {
|
|
84
|
-
router.submissions[1](v => v.filter(i => i !== submission));
|
|
85
|
-
},
|
|
86
|
-
retry() {
|
|
87
|
-
setResult(undefined);
|
|
88
|
-
const p = fn(...variables);
|
|
89
|
-
return p.then(handler(), handler(true));
|
|
37
|
+
const run = createSolidAction(async function* (context) {
|
|
38
|
+
context.optimistic?.();
|
|
39
|
+
try {
|
|
40
|
+
const value = await context.call();
|
|
41
|
+
yield;
|
|
42
|
+
return { error: false, value };
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
yield;
|
|
46
|
+
return { error: true, value: error };
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
const settled = await settleActionResult(run({
|
|
50
|
+
call: runMutation,
|
|
51
|
+
optimistic: submitHooks.size
|
|
52
|
+
? () => {
|
|
53
|
+
for (const hook of submitHooks.values())
|
|
54
|
+
hook(...variables);
|
|
90
55
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
56
|
+
: undefined
|
|
57
|
+
}));
|
|
58
|
+
const response = await handleResponse(settled.value, settled.error, router.navigatorFactory());
|
|
59
|
+
if (!response)
|
|
60
|
+
return undefined;
|
|
61
|
+
let submission;
|
|
62
|
+
submission = {
|
|
63
|
+
input: variables,
|
|
64
|
+
url,
|
|
65
|
+
result: response.data,
|
|
66
|
+
error: response.error,
|
|
67
|
+
clear() {
|
|
68
|
+
router.submissions[1](entries => entries.filter(entry => entry !== submission));
|
|
69
|
+
},
|
|
70
|
+
retry() {
|
|
71
|
+
submission.clear();
|
|
72
|
+
return current[invokeSymbol].call({ r: router, f: form }, variables, current);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
router.submissions[1](entries => [...entries, submission]);
|
|
76
|
+
for (const hook of settledHooks.values())
|
|
77
|
+
hook(submission);
|
|
78
|
+
if (response.error && !form)
|
|
79
|
+
throw response.error;
|
|
80
|
+
return response.data;
|
|
94
81
|
}
|
|
95
82
|
const o = typeof options === "string" ? { name: options } : options;
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
83
|
+
const name = o.name || (!isServer ? String(hashString(fn.toString())) : undefined);
|
|
84
|
+
const url = fn.url || (name && `https://action/${name}`) || "";
|
|
85
|
+
const wrapped = toAction(invoke, url);
|
|
86
|
+
if (name)
|
|
87
|
+
setFunctionName(wrapped, name);
|
|
88
|
+
return wrapped;
|
|
101
89
|
}
|
|
102
|
-
|
|
90
|
+
export const action = actionImpl;
|
|
91
|
+
function toAction(invoke, url, boundArgs = [], base = url, submitHooks = new Map(), settledHooks = new Map()) {
|
|
92
|
+
const fn = function (...args) {
|
|
93
|
+
return invoke.call(this, [...boundArgs, ...args], fn);
|
|
94
|
+
};
|
|
103
95
|
fn.toString = () => {
|
|
104
96
|
if (!url)
|
|
105
97
|
throw new Error("Client Actions need explicit names if server rendered");
|
|
106
98
|
return url;
|
|
107
99
|
};
|
|
108
100
|
fn.with = function (...args) {
|
|
109
|
-
const newFn = function (...passedArgs) {
|
|
110
|
-
return fn.call(this, ...args, ...passedArgs);
|
|
111
|
-
};
|
|
112
|
-
newFn.base = fn.base;
|
|
113
101
|
const uri = new URL(url, mockBase);
|
|
114
102
|
uri.searchParams.set("args", hashKey(args));
|
|
115
|
-
|
|
103
|
+
const next = toAction(invoke, (uri.origin === "https://action" ? uri.origin : "") + uri.pathname + uri.search, [...boundArgs, ...args], base, submitHooks, settledHooks);
|
|
104
|
+
return next;
|
|
105
|
+
};
|
|
106
|
+
fn.onSubmit = function (hook) {
|
|
107
|
+
const id = Symbol("actionOnSubmitHook");
|
|
108
|
+
submitHooks.set(id, hook);
|
|
109
|
+
getOwner() && onCleanup(() => submitHooks.delete(id));
|
|
110
|
+
return this;
|
|
111
|
+
};
|
|
112
|
+
fn.onSettled = function (hook) {
|
|
113
|
+
const id = Symbol("actionOnSettledHook");
|
|
114
|
+
settledHooks.set(id, hook);
|
|
115
|
+
getOwner() && onCleanup(() => settledHooks.delete(id));
|
|
116
|
+
return this;
|
|
116
117
|
};
|
|
117
118
|
fn.url = url;
|
|
119
|
+
fn.base = base;
|
|
120
|
+
fn[submitHooksSymbol] = submitHooks;
|
|
121
|
+
fn[settledHooksSymbol] = settledHooks;
|
|
122
|
+
fn[invokeSymbol] = invoke;
|
|
118
123
|
if (!isServer) {
|
|
119
124
|
actions.set(url, fn);
|
|
120
125
|
getOwner() && onCleanup(() => actions.delete(url));
|
|
@@ -122,6 +127,21 @@ function toAction(fn, url) {
|
|
|
122
127
|
return fn;
|
|
123
128
|
}
|
|
124
129
|
const hashString = (s) => s.split("").reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0);
|
|
130
|
+
async function settleActionResult(result) {
|
|
131
|
+
const value = result;
|
|
132
|
+
if (value && typeof value.then === "function") {
|
|
133
|
+
return result.then(value => value);
|
|
134
|
+
}
|
|
135
|
+
if (value && typeof value.next === "function") {
|
|
136
|
+
const iterator = value;
|
|
137
|
+
let next = await iterator.next();
|
|
138
|
+
while (!next.done) {
|
|
139
|
+
next = await iterator.next();
|
|
140
|
+
}
|
|
141
|
+
return next.value;
|
|
142
|
+
}
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
125
145
|
async function handleResponse(response, error, navigate) {
|
|
126
146
|
let data;
|
|
127
147
|
let custom;
|
package/dist/data/events.d.ts
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
import type { RouterContext } from "../types.js";
|
|
2
|
-
|
|
2
|
+
type NativeEventConfig = {
|
|
3
|
+
preload?: boolean;
|
|
4
|
+
explicitLinks?: boolean;
|
|
5
|
+
actionBase?: string;
|
|
6
|
+
transformUrl?: (url: string) => string;
|
|
7
|
+
};
|
|
8
|
+
export declare function setupNativeEvents({ preload, explicitLinks, actionBase, transformUrl }?: NativeEventConfig): (router: RouterContext) => void;
|
|
9
|
+
export {};
|