@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
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import { createRoot } from "solid-js";
|
|
2
|
-
import { vi } from "vitest";
|
|
3
|
-
import { createAsync, createAsyncStore } from "./createAsync.js";
|
|
4
|
-
vi.mock("solid-js", async () => {
|
|
5
|
-
const actual = await vi.importActual("solid-js");
|
|
6
|
-
return {
|
|
7
|
-
...actual,
|
|
8
|
-
sharedConfig: { context: null }
|
|
9
|
-
};
|
|
10
|
-
});
|
|
11
|
-
let mockSharedConfig;
|
|
12
|
-
describe("createAsync", () => {
|
|
13
|
-
beforeAll(async () => {
|
|
14
|
-
const { sharedConfig } = await import("solid-js");
|
|
15
|
-
mockSharedConfig = sharedConfig;
|
|
16
|
-
});
|
|
17
|
-
test("should create async resource with `initialValue`", async () => {
|
|
18
|
-
return createRoot(async () => {
|
|
19
|
-
const resource = createAsync(async (prev) => {
|
|
20
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
21
|
-
return prev ? prev + 1 : 1;
|
|
22
|
-
}, { initialValue: 0 });
|
|
23
|
-
expect(resource()).toBe(0);
|
|
24
|
-
expect(resource.latest).toBe(0);
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
test("should create async resource without `initialValue`", async () => {
|
|
28
|
-
return createRoot(async () => {
|
|
29
|
-
const resource = createAsync(async () => {
|
|
30
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
31
|
-
return "loaded data";
|
|
32
|
-
});
|
|
33
|
-
expect(resource()).toBeUndefined();
|
|
34
|
-
expect(resource.latest).toBeUndefined();
|
|
35
|
-
await new Promise(resolve => setTimeout(resolve, 20));
|
|
36
|
-
expect(resource()).toBe("loaded data");
|
|
37
|
-
expect(resource.latest).toBe("loaded data");
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
test("should update resource with new data", async () => {
|
|
41
|
-
return createRoot(async () => {
|
|
42
|
-
let counter = 0;
|
|
43
|
-
const resource = createAsync(async () => {
|
|
44
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
45
|
-
return ++counter;
|
|
46
|
-
});
|
|
47
|
-
await new Promise(resolve => setTimeout(resolve, 20));
|
|
48
|
-
expect(resource()).toBe(1);
|
|
49
|
-
// Trigger re-fetch - this would typically happen through some reactive source
|
|
50
|
-
// Since we can't easily trigger refetch in this test environment,
|
|
51
|
-
// we verify the structure is correct
|
|
52
|
-
expect(typeof resource).toBe("function");
|
|
53
|
-
expect(resource.latest).toBe(1);
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
test("should handle async errors", async () => {
|
|
57
|
-
return createRoot(async () => {
|
|
58
|
-
const resource = createAsync(async () => {
|
|
59
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
60
|
-
throw new Error("Async error");
|
|
61
|
-
});
|
|
62
|
-
await new Promise(resolve => setTimeout(resolve, 20));
|
|
63
|
-
/*
|
|
64
|
-
* @note Resource should handle the error gracefully
|
|
65
|
-
* The exact error handling depends on `createResource` implementation
|
|
66
|
-
*/
|
|
67
|
-
expect(typeof resource).toBe("function");
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
test("should support `deferStream` option", () => {
|
|
71
|
-
return createRoot(() => {
|
|
72
|
-
const resource = createAsync(async () => "deferred data", { deferStream: true });
|
|
73
|
-
expect(typeof resource).toBe("function");
|
|
74
|
-
expect(resource.latest).toBeUndefined();
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
test("should support `name` option for debugging", () => {
|
|
78
|
-
return createRoot(() => {
|
|
79
|
-
const resource = createAsync(async () => "named resource", { name: "test-resource" });
|
|
80
|
-
expect(typeof resource).toBe("function");
|
|
81
|
-
expect(resource.name).toBe("test-resource");
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
test("should pass previous value to fetch function", async () => {
|
|
85
|
-
return createRoot(async () => {
|
|
86
|
-
let callCount = 0;
|
|
87
|
-
let lastPrev;
|
|
88
|
-
const resource = createAsync(async (prev) => {
|
|
89
|
-
lastPrev = prev;
|
|
90
|
-
return `call-${++callCount}-prev-${prev}`;
|
|
91
|
-
}, { initialValue: "initial" });
|
|
92
|
-
expect(resource()).toBe("initial");
|
|
93
|
-
await new Promise(resolve => setTimeout(resolve, 20));
|
|
94
|
-
expect(lastPrev).toBeUndefined();
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
describe("createAsyncStore", () => {
|
|
99
|
-
test("should create async store with `initialValue`", async () => {
|
|
100
|
-
return createRoot(async () => {
|
|
101
|
-
const store = createAsyncStore(async (prev) => {
|
|
102
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
103
|
-
return { count: prev?.count ? prev.count + 1 : 1, data: "test" };
|
|
104
|
-
}, { initialValue: { count: 0, data: "initial" } });
|
|
105
|
-
expect(store()).toEqual({ count: 0, data: "initial" });
|
|
106
|
-
expect(store.latest).toEqual({ count: 0, data: "initial" });
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
test("should create async store without `initialValue`", async () => {
|
|
110
|
-
return createRoot(async () => {
|
|
111
|
-
const store = createAsyncStore(async () => {
|
|
112
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
113
|
-
return { loaded: true, message: "success" };
|
|
114
|
-
});
|
|
115
|
-
expect(store()).toBeUndefined();
|
|
116
|
-
expect(store.latest).toBeUndefined();
|
|
117
|
-
await new Promise(resolve => setTimeout(resolve, 20));
|
|
118
|
-
expect(store()).toEqual({ loaded: true, message: "success" });
|
|
119
|
-
expect(store.latest).toEqual({ loaded: true, message: "success" });
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
test("should support `reconcile` options", () => {
|
|
123
|
-
return createRoot(() => {
|
|
124
|
-
const store = createAsyncStore(async () => ({ items: [1, 2, 3] }), {
|
|
125
|
-
reconcile: { key: "id" }
|
|
126
|
-
});
|
|
127
|
-
expect(typeof store).toBe("function");
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
test("should handle complex object updates", async () => {
|
|
131
|
-
return createRoot(async () => {
|
|
132
|
-
let updateCount = 0;
|
|
133
|
-
const store = createAsyncStore(async (prev) => {
|
|
134
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
135
|
-
return {
|
|
136
|
-
...prev,
|
|
137
|
-
updateCount: ++updateCount,
|
|
138
|
-
timestamp: Date.now(),
|
|
139
|
-
nested: { value: `update-${updateCount}` }
|
|
140
|
-
};
|
|
141
|
-
}, { initialValue: { updateCount: 0, timestamp: 0, nested: { value: "initial" } } });
|
|
142
|
-
const initial = store();
|
|
143
|
-
expect(initial.updateCount).toBe(0);
|
|
144
|
-
expect(initial.nested.value).toBe("initial");
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
test("should support all `createAsync` options", () => {
|
|
148
|
-
return createRoot(() => {
|
|
149
|
-
const store = createAsyncStore(async () => ({ data: "test" }), {
|
|
150
|
-
name: "test-store",
|
|
151
|
-
deferStream: true,
|
|
152
|
-
reconcile: { merge: true }
|
|
153
|
-
});
|
|
154
|
-
expect(typeof store).toBe("function");
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
describe("MockPromise", () => {
|
|
159
|
-
test("should mock fetch during hydration", async () => {
|
|
160
|
-
mockSharedConfig.context = {};
|
|
161
|
-
return createRoot(async () => {
|
|
162
|
-
const originalFetch = window.fetch;
|
|
163
|
-
// Set up a fetch that should be mocked
|
|
164
|
-
window.fetch = () => {
|
|
165
|
-
return Promise.resolve(new Response("real fetch"));
|
|
166
|
-
};
|
|
167
|
-
const resource = createAsync(async () => {
|
|
168
|
-
const response = await fetch("/api/data");
|
|
169
|
-
return await response.text();
|
|
170
|
-
});
|
|
171
|
-
// During hydration, fetch should be mocked
|
|
172
|
-
expect(resource()).toBeUndefined();
|
|
173
|
-
window.fetch = originalFetch;
|
|
174
|
-
mockSharedConfig.context = null;
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
test("should allow real fetch outside hydration", async () => {
|
|
178
|
-
// Ensure we're not in hydration context
|
|
179
|
-
mockSharedConfig.context = null;
|
|
180
|
-
return createRoot(async () => {
|
|
181
|
-
let fetchCalled = false;
|
|
182
|
-
const originalFetch = window.fetch;
|
|
183
|
-
window.fetch = vi.fn().mockImplementation(() => {
|
|
184
|
-
fetchCalled = true;
|
|
185
|
-
return Promise.resolve(new Response("real data"));
|
|
186
|
-
});
|
|
187
|
-
createAsync(async () => {
|
|
188
|
-
const response = await fetch("/api/data");
|
|
189
|
-
return await response.text();
|
|
190
|
-
});
|
|
191
|
-
await new Promise(resolve => setTimeout(resolve, 20));
|
|
192
|
-
expect(fetchCalled).toBe(true);
|
|
193
|
-
window.fetch = originalFetch;
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
});
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { RouterContext } from "../types.js";
|
|
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 {};
|
package/dist/src/data/events.js
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { delegateEvents } from "solid-js/web";
|
|
2
|
-
import { onCleanup } from "solid-js";
|
|
3
|
-
import { actions } from "./action.js";
|
|
4
|
-
import { mockBase } from "../utils.js";
|
|
5
|
-
export function setupNativeEvents({ preload = true, explicitLinks = false, actionBase = "/_server", transformUrl } = {}) {
|
|
6
|
-
return (router) => {
|
|
7
|
-
const basePath = router.base.path();
|
|
8
|
-
const navigateFromRoute = router.navigatorFactory(router.base);
|
|
9
|
-
let preloadTimeout;
|
|
10
|
-
let lastElement;
|
|
11
|
-
function isSvg(el) {
|
|
12
|
-
return el.namespaceURI === "http://www.w3.org/2000/svg";
|
|
13
|
-
}
|
|
14
|
-
function handleAnchor(evt) {
|
|
15
|
-
if (evt.defaultPrevented ||
|
|
16
|
-
evt.button !== 0 ||
|
|
17
|
-
evt.metaKey ||
|
|
18
|
-
evt.altKey ||
|
|
19
|
-
evt.ctrlKey ||
|
|
20
|
-
evt.shiftKey)
|
|
21
|
-
return;
|
|
22
|
-
const a = evt
|
|
23
|
-
.composedPath()
|
|
24
|
-
.find(el => el instanceof Node && el.nodeName.toUpperCase() === "A");
|
|
25
|
-
if (!a || (explicitLinks && !a.hasAttribute("link")))
|
|
26
|
-
return;
|
|
27
|
-
const svg = isSvg(a);
|
|
28
|
-
const href = svg ? a.href.baseVal : a.href;
|
|
29
|
-
const target = svg ? a.target.baseVal : a.target;
|
|
30
|
-
if (target || (!href && !a.hasAttribute("state")))
|
|
31
|
-
return;
|
|
32
|
-
const rel = (a.getAttribute("rel") || "").split(/\s+/);
|
|
33
|
-
if (a.hasAttribute("download") || (rel && rel.includes("external")))
|
|
34
|
-
return;
|
|
35
|
-
const url = svg ? new URL(href, document.baseURI) : new URL(href);
|
|
36
|
-
if (url.origin !== window.location.origin ||
|
|
37
|
-
(basePath && url.pathname && !url.pathname.toLowerCase().startsWith(basePath.toLowerCase())))
|
|
38
|
-
return;
|
|
39
|
-
return [a, url];
|
|
40
|
-
}
|
|
41
|
-
function handleAnchorClick(evt) {
|
|
42
|
-
const res = handleAnchor(evt);
|
|
43
|
-
if (!res)
|
|
44
|
-
return;
|
|
45
|
-
const [a, url] = res;
|
|
46
|
-
const to = router.parsePath(url.pathname + url.search + url.hash);
|
|
47
|
-
const state = a.getAttribute("state");
|
|
48
|
-
evt.preventDefault();
|
|
49
|
-
navigateFromRoute(to, {
|
|
50
|
-
resolve: false,
|
|
51
|
-
replace: a.hasAttribute("replace"),
|
|
52
|
-
scroll: !a.hasAttribute("noscroll"),
|
|
53
|
-
state: state ? JSON.parse(state) : undefined
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
function handleAnchorPreload(evt) {
|
|
57
|
-
const res = handleAnchor(evt);
|
|
58
|
-
if (!res)
|
|
59
|
-
return;
|
|
60
|
-
const [a, url] = res;
|
|
61
|
-
transformUrl && (url.pathname = transformUrl(url.pathname));
|
|
62
|
-
router.preloadRoute(url, a.getAttribute("preload") !== "false");
|
|
63
|
-
}
|
|
64
|
-
function handleAnchorMove(evt) {
|
|
65
|
-
clearTimeout(preloadTimeout);
|
|
66
|
-
const res = handleAnchor(evt);
|
|
67
|
-
if (!res)
|
|
68
|
-
return (lastElement = null);
|
|
69
|
-
const [a, url] = res;
|
|
70
|
-
if (lastElement === a)
|
|
71
|
-
return;
|
|
72
|
-
transformUrl && (url.pathname = transformUrl(url.pathname));
|
|
73
|
-
preloadTimeout = setTimeout(() => {
|
|
74
|
-
router.preloadRoute(url, a.getAttribute("preload") !== "false");
|
|
75
|
-
lastElement = a;
|
|
76
|
-
}, 20);
|
|
77
|
-
}
|
|
78
|
-
function handleFormSubmit(evt) {
|
|
79
|
-
if (evt.defaultPrevented)
|
|
80
|
-
return;
|
|
81
|
-
let actionRef = evt.submitter && evt.submitter.hasAttribute("formaction")
|
|
82
|
-
? evt.submitter.getAttribute("formaction")
|
|
83
|
-
: evt.target.getAttribute("action");
|
|
84
|
-
if (!actionRef)
|
|
85
|
-
return;
|
|
86
|
-
if (!actionRef.startsWith("https://action/")) {
|
|
87
|
-
// normalize server actions
|
|
88
|
-
const url = new URL(actionRef, mockBase);
|
|
89
|
-
actionRef = router.parsePath(url.pathname + url.search);
|
|
90
|
-
if (!actionRef.startsWith(actionBase))
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
if (evt.target.method.toUpperCase() !== "POST")
|
|
94
|
-
throw new Error("Only POST forms are supported for Actions");
|
|
95
|
-
const handler = actions.get(actionRef);
|
|
96
|
-
if (handler) {
|
|
97
|
-
evt.preventDefault();
|
|
98
|
-
const data = new FormData(evt.target, evt.submitter);
|
|
99
|
-
handler.call({ r: router, f: evt.target }, evt.target.enctype === "multipart/form-data"
|
|
100
|
-
? data
|
|
101
|
-
: new URLSearchParams(data));
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
// ensure delegated event run first
|
|
105
|
-
delegateEvents(["click", "submit"]);
|
|
106
|
-
document.addEventListener("click", handleAnchorClick);
|
|
107
|
-
if (preload) {
|
|
108
|
-
document.addEventListener("mousemove", handleAnchorMove, { passive: true });
|
|
109
|
-
document.addEventListener("focusin", handleAnchorPreload, { passive: true });
|
|
110
|
-
document.addEventListener("touchstart", handleAnchorPreload, { passive: true });
|
|
111
|
-
}
|
|
112
|
-
document.addEventListener("submit", handleFormSubmit);
|
|
113
|
-
onCleanup(() => {
|
|
114
|
-
document.removeEventListener("click", handleAnchorClick);
|
|
115
|
-
if (preload) {
|
|
116
|
-
document.removeEventListener("mousemove", handleAnchorMove);
|
|
117
|
-
document.removeEventListener("focusin", handleAnchorPreload);
|
|
118
|
-
document.removeEventListener("touchstart", handleAnchorPreload);
|
|
119
|
-
}
|
|
120
|
-
document.removeEventListener("submit", handleFormSubmit);
|
|
121
|
-
});
|
|
122
|
-
};
|
|
123
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|