@ereo/client 0.1.6
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 +93 -0
- package/dist/error-boundary.d.ts +215 -0
- package/dist/error-boundary.d.ts.map +1 -0
- package/dist/form.d.ts +436 -0
- package/dist/form.d.ts.map +1 -0
- package/dist/hooks.d.ts +261 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hydration.d.ts +67 -0
- package/dist/hydration.d.ts.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2081 -0
- package/dist/islands.d.ts +103 -0
- package/dist/islands.d.ts.map +1 -0
- package/dist/link.d.ts +91 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/navigation.d.ts +121 -0
- package/dist/navigation.d.ts.map +1 -0
- package/dist/prefetch.d.ts +57 -0
- package/dist/prefetch.d.ts.map +1 -0
- package/dist/typed-link.d.ts +189 -0
- package/dist/typed-link.d.ts.map +1 -0
- package/dist/typed-navigate.d.ts +238 -0
- package/dist/typed-navigate.d.ts.map +1 -0
- package/package.json +46 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2081 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined")
|
|
5
|
+
return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// src/hydration.ts
|
|
10
|
+
function parseHydrationDirective(props) {
|
|
11
|
+
if (props["client:load"]) {
|
|
12
|
+
return { strategy: "load" };
|
|
13
|
+
}
|
|
14
|
+
if (props["client:idle"]) {
|
|
15
|
+
return { strategy: "idle" };
|
|
16
|
+
}
|
|
17
|
+
if (props["client:visible"]) {
|
|
18
|
+
return { strategy: "visible" };
|
|
19
|
+
}
|
|
20
|
+
if (props["client:media"]) {
|
|
21
|
+
return { strategy: "media", media: props["client:media"] };
|
|
22
|
+
}
|
|
23
|
+
if (props["client:only"]) {
|
|
24
|
+
return { strategy: "load" };
|
|
25
|
+
}
|
|
26
|
+
return { strategy: "none" };
|
|
27
|
+
}
|
|
28
|
+
function shouldHydrate(strategy, media) {
|
|
29
|
+
switch (strategy) {
|
|
30
|
+
case "load":
|
|
31
|
+
return true;
|
|
32
|
+
case "idle":
|
|
33
|
+
return false;
|
|
34
|
+
case "visible":
|
|
35
|
+
return false;
|
|
36
|
+
case "media":
|
|
37
|
+
if (!media)
|
|
38
|
+
return false;
|
|
39
|
+
return () => window.matchMedia(media).matches;
|
|
40
|
+
case "none":
|
|
41
|
+
default:
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function createHydrationTrigger(strategy, element, onHydrate, media) {
|
|
46
|
+
let cleanup = null;
|
|
47
|
+
switch (strategy) {
|
|
48
|
+
case "load":
|
|
49
|
+
onHydrate();
|
|
50
|
+
break;
|
|
51
|
+
case "idle":
|
|
52
|
+
if ("requestIdleCallback" in window) {
|
|
53
|
+
const id = requestIdleCallback(onHydrate);
|
|
54
|
+
cleanup = () => cancelIdleCallback(id);
|
|
55
|
+
} else {
|
|
56
|
+
const id = setTimeout(onHydrate, 200);
|
|
57
|
+
cleanup = () => clearTimeout(id);
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
case "visible":
|
|
61
|
+
const observer = new IntersectionObserver((entries) => {
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
if (entry.isIntersecting) {
|
|
64
|
+
observer.disconnect();
|
|
65
|
+
onHydrate();
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}, { rootMargin: "200px" });
|
|
70
|
+
observer.observe(element);
|
|
71
|
+
cleanup = () => observer.disconnect();
|
|
72
|
+
break;
|
|
73
|
+
case "media":
|
|
74
|
+
if (!media)
|
|
75
|
+
break;
|
|
76
|
+
const mql = window.matchMedia(media);
|
|
77
|
+
const handler = (e) => {
|
|
78
|
+
if (e.matches) {
|
|
79
|
+
mql.removeEventListener("change", handler);
|
|
80
|
+
onHydrate();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
if (mql.matches) {
|
|
84
|
+
onHydrate();
|
|
85
|
+
} else {
|
|
86
|
+
mql.addEventListener("change", handler);
|
|
87
|
+
cleanup = () => mql.removeEventListener("change", handler);
|
|
88
|
+
}
|
|
89
|
+
break;
|
|
90
|
+
case "none":
|
|
91
|
+
default:
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
return () => {
|
|
95
|
+
if (cleanup)
|
|
96
|
+
cleanup();
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function stripHydrationProps(props) {
|
|
100
|
+
const {
|
|
101
|
+
"client:load": _load,
|
|
102
|
+
"client:idle": _idle,
|
|
103
|
+
"client:visible": _visible,
|
|
104
|
+
"client:media": _media,
|
|
105
|
+
"client:only": _only,
|
|
106
|
+
...rest
|
|
107
|
+
} = props;
|
|
108
|
+
return rest;
|
|
109
|
+
}
|
|
110
|
+
var islandCounter = 0;
|
|
111
|
+
function generateIslandId() {
|
|
112
|
+
return `island-${++islandCounter}`;
|
|
113
|
+
}
|
|
114
|
+
function resetIslandCounter() {
|
|
115
|
+
islandCounter = 0;
|
|
116
|
+
}
|
|
117
|
+
function getIslandCount() {
|
|
118
|
+
return islandCounter;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/islands.ts
|
|
122
|
+
class IslandRegistry {
|
|
123
|
+
islands = new Map;
|
|
124
|
+
cleanups = new Map;
|
|
125
|
+
register(id, component, props, strategy, element, media) {
|
|
126
|
+
this.islands.set(id, {
|
|
127
|
+
id,
|
|
128
|
+
component,
|
|
129
|
+
props,
|
|
130
|
+
strategy,
|
|
131
|
+
media,
|
|
132
|
+
element,
|
|
133
|
+
hydrated: false
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
get(id) {
|
|
137
|
+
return this.islands.get(id);
|
|
138
|
+
}
|
|
139
|
+
markHydrated(id) {
|
|
140
|
+
const island = this.islands.get(id);
|
|
141
|
+
if (island) {
|
|
142
|
+
island.hydrated = true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
isHydrated(id) {
|
|
146
|
+
return this.islands.get(id)?.hydrated ?? false;
|
|
147
|
+
}
|
|
148
|
+
setCleanup(id, cleanup) {
|
|
149
|
+
this.cleanups.set(id, cleanup);
|
|
150
|
+
}
|
|
151
|
+
cleanup(id) {
|
|
152
|
+
const cleanupFn = this.cleanups.get(id);
|
|
153
|
+
if (cleanupFn) {
|
|
154
|
+
cleanupFn();
|
|
155
|
+
this.cleanups.delete(id);
|
|
156
|
+
}
|
|
157
|
+
this.islands.delete(id);
|
|
158
|
+
}
|
|
159
|
+
cleanupAll() {
|
|
160
|
+
for (const id of this.islands.keys()) {
|
|
161
|
+
this.cleanup(id);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
getAll() {
|
|
165
|
+
return Array.from(this.islands.values());
|
|
166
|
+
}
|
|
167
|
+
getByStrategy(strategy) {
|
|
168
|
+
return this.getAll().filter((i) => i.strategy === strategy);
|
|
169
|
+
}
|
|
170
|
+
getPending() {
|
|
171
|
+
return this.getAll().filter((i) => !i.hydrated);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
var islandRegistry = new IslandRegistry;
|
|
175
|
+
async function hydrateIslands() {
|
|
176
|
+
const { hydrateRoot } = await import("react-dom/client");
|
|
177
|
+
const { createElement } = await import("react");
|
|
178
|
+
const islandElements = document.querySelectorAll("[data-island]");
|
|
179
|
+
for (const element of islandElements) {
|
|
180
|
+
const islandId = element.getAttribute("data-island");
|
|
181
|
+
const componentName = element.getAttribute("data-component");
|
|
182
|
+
const propsJson = element.getAttribute("data-props");
|
|
183
|
+
const strategy = element.getAttribute("data-strategy") || "load";
|
|
184
|
+
const media = element.getAttribute("data-media") || undefined;
|
|
185
|
+
if (!islandId || !componentName)
|
|
186
|
+
continue;
|
|
187
|
+
const component = getIslandComponent(componentName);
|
|
188
|
+
if (!component) {
|
|
189
|
+
console.warn(`Island component not found: ${componentName}`);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const props = propsJson ? JSON.parse(propsJson) : {};
|
|
193
|
+
islandRegistry.register(islandId, component, props, strategy, element, media);
|
|
194
|
+
const cleanup = createHydrationTrigger(strategy, element, () => {
|
|
195
|
+
if (islandRegistry.isHydrated(islandId))
|
|
196
|
+
return;
|
|
197
|
+
const root = hydrateRoot(element, createElement(component, props));
|
|
198
|
+
islandRegistry.markHydrated(islandId);
|
|
199
|
+
islandRegistry.setCleanup(islandId, () => root.unmount());
|
|
200
|
+
}, media);
|
|
201
|
+
islandRegistry.setCleanup(islandId, cleanup);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
var componentRegistry = new Map;
|
|
205
|
+
function registerIslandComponent(name, component) {
|
|
206
|
+
componentRegistry.set(name, component);
|
|
207
|
+
}
|
|
208
|
+
function getIslandComponent(name) {
|
|
209
|
+
return componentRegistry.get(name);
|
|
210
|
+
}
|
|
211
|
+
function registerIslandComponents(components) {
|
|
212
|
+
for (const [name, component] of Object.entries(components)) {
|
|
213
|
+
registerIslandComponent(name, component);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function createIsland(component, name) {
|
|
217
|
+
registerIslandComponent(name, component);
|
|
218
|
+
return function IslandWrapper(props) {
|
|
219
|
+
const { strategy, media } = parseHydrationDirective(props);
|
|
220
|
+
const cleanProps = stripHydrationProps(props);
|
|
221
|
+
const islandId = generateIslandId();
|
|
222
|
+
return {
|
|
223
|
+
type: "div",
|
|
224
|
+
props: {
|
|
225
|
+
"data-island": islandId,
|
|
226
|
+
"data-component": name,
|
|
227
|
+
"data-props": JSON.stringify(cleanProps),
|
|
228
|
+
"data-strategy": strategy,
|
|
229
|
+
"data-media": media || undefined,
|
|
230
|
+
children: {
|
|
231
|
+
type: component,
|
|
232
|
+
props: cleanProps
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function initializeIslands() {
|
|
239
|
+
if (typeof document === "undefined")
|
|
240
|
+
return;
|
|
241
|
+
if (document.readyState === "loading") {
|
|
242
|
+
document.addEventListener("DOMContentLoaded", () => hydrateIslands());
|
|
243
|
+
} else {
|
|
244
|
+
hydrateIslands();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function cleanupIslands() {
|
|
248
|
+
islandRegistry.cleanupAll();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/navigation.ts
|
|
252
|
+
class ClientRouter {
|
|
253
|
+
listeners = new Set;
|
|
254
|
+
currentState;
|
|
255
|
+
constructor() {
|
|
256
|
+
this.currentState = this.getStateFromLocation();
|
|
257
|
+
this.setupPopState();
|
|
258
|
+
}
|
|
259
|
+
getStateFromLocation() {
|
|
260
|
+
if (typeof window === "undefined") {
|
|
261
|
+
return { pathname: "/", search: "", hash: "" };
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
pathname: window.location.pathname,
|
|
265
|
+
search: window.location.search,
|
|
266
|
+
hash: window.location.hash,
|
|
267
|
+
state: window.history.state
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
setupPopState() {
|
|
271
|
+
if (typeof window === "undefined")
|
|
272
|
+
return;
|
|
273
|
+
window.addEventListener("popstate", (event) => {
|
|
274
|
+
const from = this.currentState;
|
|
275
|
+
this.currentState = this.getStateFromLocation();
|
|
276
|
+
this.notify({
|
|
277
|
+
type: "pop",
|
|
278
|
+
from,
|
|
279
|
+
to: this.currentState
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
async navigate(to, options = {}) {
|
|
284
|
+
if (typeof window === "undefined")
|
|
285
|
+
return;
|
|
286
|
+
const url = new URL(to, window.location.origin);
|
|
287
|
+
const from = this.currentState;
|
|
288
|
+
const newState = {
|
|
289
|
+
pathname: url.pathname,
|
|
290
|
+
search: url.search,
|
|
291
|
+
hash: url.hash,
|
|
292
|
+
state: options.state
|
|
293
|
+
};
|
|
294
|
+
if (options.replace) {
|
|
295
|
+
window.history.replaceState(options.state, "", to);
|
|
296
|
+
} else {
|
|
297
|
+
window.history.pushState(options.state, "", to);
|
|
298
|
+
}
|
|
299
|
+
this.currentState = newState;
|
|
300
|
+
this.notify({
|
|
301
|
+
type: options.replace ? "replace" : "push",
|
|
302
|
+
from,
|
|
303
|
+
to: newState
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
back() {
|
|
307
|
+
if (typeof window !== "undefined") {
|
|
308
|
+
window.history.back();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
forward() {
|
|
312
|
+
if (typeof window !== "undefined") {
|
|
313
|
+
window.history.forward();
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
go(delta) {
|
|
317
|
+
if (typeof window !== "undefined") {
|
|
318
|
+
window.history.go(delta);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
subscribe(listener) {
|
|
322
|
+
this.listeners.add(listener);
|
|
323
|
+
return () => this.listeners.delete(listener);
|
|
324
|
+
}
|
|
325
|
+
notify(event) {
|
|
326
|
+
for (const listener of this.listeners) {
|
|
327
|
+
listener(event);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
getState() {
|
|
331
|
+
return this.currentState;
|
|
332
|
+
}
|
|
333
|
+
isActive(path, exact = false) {
|
|
334
|
+
if (exact) {
|
|
335
|
+
return this.currentState.pathname === path;
|
|
336
|
+
}
|
|
337
|
+
return this.currentState.pathname.startsWith(path);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
var router = new ClientRouter;
|
|
341
|
+
function navigate(to, options) {
|
|
342
|
+
return router.navigate(to, options);
|
|
343
|
+
}
|
|
344
|
+
function goBack() {
|
|
345
|
+
router.back();
|
|
346
|
+
}
|
|
347
|
+
function goForward() {
|
|
348
|
+
router.forward();
|
|
349
|
+
}
|
|
350
|
+
function onNavigate(listener) {
|
|
351
|
+
return router.subscribe(listener);
|
|
352
|
+
}
|
|
353
|
+
function getNavigationState() {
|
|
354
|
+
return router.getState();
|
|
355
|
+
}
|
|
356
|
+
async function fetchLoaderData(pathname, params) {
|
|
357
|
+
const url = new URL(pathname, window.location.origin);
|
|
358
|
+
if (params) {
|
|
359
|
+
for (const [key, value] of Object.entries(params)) {
|
|
360
|
+
if (value !== undefined) {
|
|
361
|
+
url.searchParams.set(key, String(value));
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const response = await fetch(url.toString(), {
|
|
366
|
+
headers: {
|
|
367
|
+
Accept: "application/json"
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
if (!response.ok) {
|
|
371
|
+
throw new Error(`Failed to fetch loader data: ${response.status}`);
|
|
372
|
+
}
|
|
373
|
+
const result = await response.json();
|
|
374
|
+
return result.data;
|
|
375
|
+
}
|
|
376
|
+
async function submitAction(pathname, formData, options = {}) {
|
|
377
|
+
const response = await fetch(pathname, {
|
|
378
|
+
method: options.method || "POST",
|
|
379
|
+
body: formData,
|
|
380
|
+
headers: {
|
|
381
|
+
Accept: "application/json"
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
if (!response.ok) {
|
|
385
|
+
throw new Error(`Action failed: ${response.status}`);
|
|
386
|
+
}
|
|
387
|
+
return response.json();
|
|
388
|
+
}
|
|
389
|
+
function setupScrollRestoration() {
|
|
390
|
+
if (typeof window === "undefined")
|
|
391
|
+
return;
|
|
392
|
+
if ("scrollRestoration" in history) {
|
|
393
|
+
history.scrollRestoration = "manual";
|
|
394
|
+
}
|
|
395
|
+
const scrollPositions = new Map;
|
|
396
|
+
router.subscribe((event) => {
|
|
397
|
+
scrollPositions.set(event.from.pathname, window.scrollY);
|
|
398
|
+
if (event.type === "pop") {
|
|
399
|
+
const savedPosition = scrollPositions.get(event.to.pathname);
|
|
400
|
+
if (savedPosition !== undefined) {
|
|
401
|
+
requestAnimationFrame(() => {
|
|
402
|
+
window.scrollTo(0, savedPosition);
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
} else {
|
|
406
|
+
window.scrollTo(0, 0);
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// src/prefetch.ts
|
|
412
|
+
var prefetchCache = new Map;
|
|
413
|
+
var defaultOptions = {
|
|
414
|
+
strategy: "hover",
|
|
415
|
+
cacheDuration: 30000,
|
|
416
|
+
threshold: 0
|
|
417
|
+
};
|
|
418
|
+
function isCacheValid(entry, cacheDuration) {
|
|
419
|
+
return Date.now() - entry.timestamp < cacheDuration;
|
|
420
|
+
}
|
|
421
|
+
async function prefetch(url) {
|
|
422
|
+
const cached = prefetchCache.get(url);
|
|
423
|
+
if (cached && isCacheValid(cached, defaultOptions.cacheDuration)) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
const entry = {
|
|
427
|
+
url,
|
|
428
|
+
timestamp: Date.now(),
|
|
429
|
+
loading: true
|
|
430
|
+
};
|
|
431
|
+
prefetchCache.set(url, entry);
|
|
432
|
+
try {
|
|
433
|
+
const response = await fetch(url, {
|
|
434
|
+
method: "GET",
|
|
435
|
+
headers: {
|
|
436
|
+
Accept: "application/json",
|
|
437
|
+
"X-Prefetch": "true"
|
|
438
|
+
},
|
|
439
|
+
priority: "low"
|
|
440
|
+
});
|
|
441
|
+
if (response.ok) {
|
|
442
|
+
entry.data = await response.json();
|
|
443
|
+
} else {
|
|
444
|
+
entry.error = new Error(`Prefetch failed: ${response.status}`);
|
|
445
|
+
}
|
|
446
|
+
} catch (error) {
|
|
447
|
+
entry.error = error instanceof Error ? error : new Error("Prefetch failed");
|
|
448
|
+
} finally {
|
|
449
|
+
entry.loading = false;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
function getPrefetchedData(url) {
|
|
453
|
+
const entry = prefetchCache.get(url);
|
|
454
|
+
if (entry && isCacheValid(entry, defaultOptions.cacheDuration) && entry.data) {
|
|
455
|
+
return entry.data;
|
|
456
|
+
}
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
function clearPrefetchCache() {
|
|
460
|
+
prefetchCache.clear();
|
|
461
|
+
}
|
|
462
|
+
function setupLinkPrefetch(element, options = {}) {
|
|
463
|
+
const { strategy, threshold } = { ...defaultOptions, ...options };
|
|
464
|
+
const href = element.href;
|
|
465
|
+
if (!href || !href.startsWith(window.location.origin)) {
|
|
466
|
+
return () => {};
|
|
467
|
+
}
|
|
468
|
+
const url = new URL(href).pathname;
|
|
469
|
+
let cleanup = null;
|
|
470
|
+
switch (strategy) {
|
|
471
|
+
case "hover": {
|
|
472
|
+
const onMouseEnter = () => prefetch(url);
|
|
473
|
+
element.addEventListener("mouseenter", onMouseEnter);
|
|
474
|
+
cleanup = () => element.removeEventListener("mouseenter", onMouseEnter);
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
case "viewport": {
|
|
478
|
+
const observer = new IntersectionObserver((entries) => {
|
|
479
|
+
for (const entry of entries) {
|
|
480
|
+
if (entry.isIntersecting) {
|
|
481
|
+
prefetch(url);
|
|
482
|
+
observer.disconnect();
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}, { threshold });
|
|
487
|
+
observer.observe(element);
|
|
488
|
+
cleanup = () => observer.disconnect();
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
case "eager": {
|
|
492
|
+
prefetch(url);
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
case "none":
|
|
496
|
+
default:
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
return () => {
|
|
500
|
+
if (cleanup)
|
|
501
|
+
cleanup();
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
function setupAutoPrefetch(options = {}) {
|
|
505
|
+
if (typeof document === "undefined")
|
|
506
|
+
return () => {};
|
|
507
|
+
const cleanups = [];
|
|
508
|
+
const links = document.querySelectorAll('a[href^="/"]');
|
|
509
|
+
links.forEach((link) => {
|
|
510
|
+
cleanups.push(setupLinkPrefetch(link, options));
|
|
511
|
+
});
|
|
512
|
+
const observer = new MutationObserver((mutations) => {
|
|
513
|
+
for (const mutation of mutations) {
|
|
514
|
+
for (const node of mutation.addedNodes) {
|
|
515
|
+
if (node instanceof HTMLAnchorElement && node.href.startsWith("/")) {
|
|
516
|
+
cleanups.push(setupLinkPrefetch(node, options));
|
|
517
|
+
}
|
|
518
|
+
if (node instanceof Element) {
|
|
519
|
+
const newLinks = node.querySelectorAll('a[href^="/"]');
|
|
520
|
+
newLinks.forEach((link) => {
|
|
521
|
+
cleanups.push(setupLinkPrefetch(link, options));
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
528
|
+
return () => {
|
|
529
|
+
observer.disconnect();
|
|
530
|
+
cleanups.forEach((cleanup) => cleanup());
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
async function prefetchAll(urls) {
|
|
534
|
+
await Promise.all(urls.map(prefetch));
|
|
535
|
+
}
|
|
536
|
+
function isPrefetching(url) {
|
|
537
|
+
const entry = prefetchCache.get(url);
|
|
538
|
+
return entry?.loading ?? false;
|
|
539
|
+
}
|
|
540
|
+
function isPrefetched(url) {
|
|
541
|
+
const entry = prefetchCache.get(url);
|
|
542
|
+
return entry?.data !== undefined;
|
|
543
|
+
}
|
|
544
|
+
// src/hooks.ts
|
|
545
|
+
import {
|
|
546
|
+
createContext,
|
|
547
|
+
useContext,
|
|
548
|
+
useState,
|
|
549
|
+
useCallback,
|
|
550
|
+
useMemo,
|
|
551
|
+
createElement
|
|
552
|
+
} from "react";
|
|
553
|
+
var LoaderDataContext = createContext(null);
|
|
554
|
+
var ActionDataContext = createContext(null);
|
|
555
|
+
var NavigationContext = createContext(null);
|
|
556
|
+
var ErrorContext = createContext(null);
|
|
557
|
+
function useLoaderData() {
|
|
558
|
+
const context = useContext(LoaderDataContext);
|
|
559
|
+
if (context === null) {
|
|
560
|
+
throw new Error("useLoaderData must be used within an EreoProvider. " + "Make sure your component is wrapped with <EreoProvider>.");
|
|
561
|
+
}
|
|
562
|
+
return context.data;
|
|
563
|
+
}
|
|
564
|
+
function useActionData() {
|
|
565
|
+
const context = useContext(ActionDataContext);
|
|
566
|
+
if (context === null) {
|
|
567
|
+
throw new Error("useActionData must be used within an EreoProvider. " + "Make sure your component is wrapped with <EreoProvider>.");
|
|
568
|
+
}
|
|
569
|
+
return context.data;
|
|
570
|
+
}
|
|
571
|
+
function useNavigation() {
|
|
572
|
+
const context = useContext(NavigationContext);
|
|
573
|
+
if (context === null) {
|
|
574
|
+
throw new Error("useNavigation must be used within an EreoProvider. " + "Make sure your component is wrapped with <EreoProvider>.");
|
|
575
|
+
}
|
|
576
|
+
return context.state;
|
|
577
|
+
}
|
|
578
|
+
function useError() {
|
|
579
|
+
const context = useContext(ErrorContext);
|
|
580
|
+
if (context === null) {
|
|
581
|
+
throw new Error("useError must be used within an EreoProvider. " + "Make sure your component is wrapped with <EreoProvider>.");
|
|
582
|
+
}
|
|
583
|
+
return context.error;
|
|
584
|
+
}
|
|
585
|
+
function LoaderDataProvider({
|
|
586
|
+
children,
|
|
587
|
+
initialData
|
|
588
|
+
}) {
|
|
589
|
+
const [data, setData] = useState(initialData);
|
|
590
|
+
const value = useMemo(() => ({ data, setData }), [data]);
|
|
591
|
+
return createElement(LoaderDataContext.Provider, { value }, children);
|
|
592
|
+
}
|
|
593
|
+
function ActionDataProvider({
|
|
594
|
+
children,
|
|
595
|
+
initialData
|
|
596
|
+
}) {
|
|
597
|
+
const [data, setData] = useState(initialData);
|
|
598
|
+
const clearData = useCallback(() => {
|
|
599
|
+
setData(undefined);
|
|
600
|
+
}, []);
|
|
601
|
+
const value = useMemo(() => ({ data, setData, clearData }), [data, clearData]);
|
|
602
|
+
return createElement(ActionDataContext.Provider, { value }, children);
|
|
603
|
+
}
|
|
604
|
+
var defaultNavigationState = {
|
|
605
|
+
status: "idle"
|
|
606
|
+
};
|
|
607
|
+
function NavigationProvider({
|
|
608
|
+
children,
|
|
609
|
+
initialState = defaultNavigationState
|
|
610
|
+
}) {
|
|
611
|
+
const [state, setState] = useState(initialState);
|
|
612
|
+
const startLoading = useCallback((location) => {
|
|
613
|
+
setState({
|
|
614
|
+
status: "loading",
|
|
615
|
+
location
|
|
616
|
+
});
|
|
617
|
+
}, []);
|
|
618
|
+
const startSubmitting = useCallback((options) => {
|
|
619
|
+
setState({
|
|
620
|
+
status: "submitting",
|
|
621
|
+
location: options.location,
|
|
622
|
+
formData: options.formData,
|
|
623
|
+
formMethod: options.formMethod,
|
|
624
|
+
formAction: options.formAction
|
|
625
|
+
});
|
|
626
|
+
}, []);
|
|
627
|
+
const complete = useCallback(() => {
|
|
628
|
+
setState({ status: "idle" });
|
|
629
|
+
}, []);
|
|
630
|
+
const value = useMemo(() => ({
|
|
631
|
+
state,
|
|
632
|
+
setState,
|
|
633
|
+
startLoading,
|
|
634
|
+
startSubmitting,
|
|
635
|
+
complete
|
|
636
|
+
}), [state, startLoading, startSubmitting, complete]);
|
|
637
|
+
return createElement(NavigationContext.Provider, { value }, children);
|
|
638
|
+
}
|
|
639
|
+
function ErrorProvider({
|
|
640
|
+
children,
|
|
641
|
+
initialError
|
|
642
|
+
}) {
|
|
643
|
+
const [error, setError] = useState(initialError);
|
|
644
|
+
const clearError = useCallback(() => {
|
|
645
|
+
setError(undefined);
|
|
646
|
+
}, []);
|
|
647
|
+
const value = useMemo(() => ({ error, setError, clearError }), [error, clearError]);
|
|
648
|
+
return createElement(ErrorContext.Provider, { value }, children);
|
|
649
|
+
}
|
|
650
|
+
function EreoProvider({
|
|
651
|
+
children,
|
|
652
|
+
loaderData,
|
|
653
|
+
actionData,
|
|
654
|
+
navigationState,
|
|
655
|
+
error
|
|
656
|
+
}) {
|
|
657
|
+
return createElement(ErrorProvider, { initialError: error, children: createElement(NavigationProvider, { initialState: navigationState, children: createElement(ActionDataProvider, { initialData: actionData, children: createElement(LoaderDataProvider, { initialData: loaderData, children }) }) }) });
|
|
658
|
+
}
|
|
659
|
+
function useLoaderDataContext() {
|
|
660
|
+
const context = useContext(LoaderDataContext);
|
|
661
|
+
if (context === null) {
|
|
662
|
+
throw new Error("useLoaderDataContext must be used within an EreoProvider");
|
|
663
|
+
}
|
|
664
|
+
return context;
|
|
665
|
+
}
|
|
666
|
+
function useActionDataContext() {
|
|
667
|
+
const context = useContext(ActionDataContext);
|
|
668
|
+
if (context === null) {
|
|
669
|
+
throw new Error("useActionDataContext must be used within an EreoProvider");
|
|
670
|
+
}
|
|
671
|
+
return context;
|
|
672
|
+
}
|
|
673
|
+
function useNavigationContext() {
|
|
674
|
+
const context = useContext(NavigationContext);
|
|
675
|
+
if (context === null) {
|
|
676
|
+
throw new Error("useNavigationContext must be used within an EreoProvider");
|
|
677
|
+
}
|
|
678
|
+
return context;
|
|
679
|
+
}
|
|
680
|
+
function useErrorContext() {
|
|
681
|
+
const context = useContext(ErrorContext);
|
|
682
|
+
if (context === null) {
|
|
683
|
+
throw new Error("useErrorContext must be used within an EreoProvider");
|
|
684
|
+
}
|
|
685
|
+
return context;
|
|
686
|
+
}
|
|
687
|
+
// src/link.tsx
|
|
688
|
+
import * as React from "react";
|
|
689
|
+
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
690
|
+
function isExternalUrl(url) {
|
|
691
|
+
if (typeof window === "undefined")
|
|
692
|
+
return false;
|
|
693
|
+
if (url.startsWith("/") && !url.startsWith("//") || url.startsWith(".")) {
|
|
694
|
+
return false;
|
|
695
|
+
}
|
|
696
|
+
try {
|
|
697
|
+
const parsed = new URL(url, window.location.origin);
|
|
698
|
+
return parsed.origin !== window.location.origin;
|
|
699
|
+
} catch {
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
function shouldNavigate(event) {
|
|
704
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
if (event.button !== 0) {
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
if (event.defaultPrevented) {
|
|
711
|
+
return false;
|
|
712
|
+
}
|
|
713
|
+
return true;
|
|
714
|
+
}
|
|
715
|
+
var Link = React.forwardRef(function Link2({
|
|
716
|
+
to,
|
|
717
|
+
prefetch: prefetchStrategy = "intent",
|
|
718
|
+
replace = false,
|
|
719
|
+
preventScrollReset = false,
|
|
720
|
+
state,
|
|
721
|
+
reloadDocument = false,
|
|
722
|
+
onClick,
|
|
723
|
+
onMouseEnter,
|
|
724
|
+
onFocus,
|
|
725
|
+
children,
|
|
726
|
+
...rest
|
|
727
|
+
}, ref) {
|
|
728
|
+
const internalRef = React.useRef(null);
|
|
729
|
+
const resolvedRef = ref || internalRef;
|
|
730
|
+
const hasPrefetched = React.useRef(false);
|
|
731
|
+
const isExternal = isExternalUrl(to);
|
|
732
|
+
const triggerPrefetch = React.useCallback(() => {
|
|
733
|
+
if (hasPrefetched.current || isExternal || prefetchStrategy === "none") {
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
hasPrefetched.current = true;
|
|
737
|
+
prefetch(to);
|
|
738
|
+
}, [to, isExternal, prefetchStrategy]);
|
|
739
|
+
const handleClick = React.useCallback((event) => {
|
|
740
|
+
onClick?.(event);
|
|
741
|
+
if (isExternal || reloadDocument) {
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
if (!shouldNavigate(event)) {
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
const target = event.currentTarget.getAttribute("target");
|
|
748
|
+
if (target && target !== "_self") {
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
event.preventDefault();
|
|
752
|
+
navigate(to, { replace, state });
|
|
753
|
+
if (!preventScrollReset && typeof window !== "undefined") {
|
|
754
|
+
window.scrollTo(0, 0);
|
|
755
|
+
}
|
|
756
|
+
}, [onClick, to, replace, state, isExternal, reloadDocument, preventScrollReset]);
|
|
757
|
+
const handleMouseEnter = React.useCallback((event) => {
|
|
758
|
+
onMouseEnter?.(event);
|
|
759
|
+
if (prefetchStrategy === "intent") {
|
|
760
|
+
triggerPrefetch();
|
|
761
|
+
}
|
|
762
|
+
}, [onMouseEnter, prefetchStrategy, triggerPrefetch]);
|
|
763
|
+
const handleFocus = React.useCallback((event) => {
|
|
764
|
+
onFocus?.(event);
|
|
765
|
+
if (prefetchStrategy === "intent") {
|
|
766
|
+
triggerPrefetch();
|
|
767
|
+
}
|
|
768
|
+
}, [onFocus, prefetchStrategy, triggerPrefetch]);
|
|
769
|
+
React.useEffect(() => {
|
|
770
|
+
if (prefetchStrategy === "render") {
|
|
771
|
+
triggerPrefetch();
|
|
772
|
+
}
|
|
773
|
+
}, [prefetchStrategy, triggerPrefetch]);
|
|
774
|
+
React.useEffect(() => {
|
|
775
|
+
if (prefetchStrategy !== "viewport" || typeof IntersectionObserver === "undefined") {
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
const element = resolvedRef.current;
|
|
779
|
+
if (!element)
|
|
780
|
+
return;
|
|
781
|
+
const observer = new IntersectionObserver((entries) => {
|
|
782
|
+
for (const entry of entries) {
|
|
783
|
+
if (entry.isIntersecting) {
|
|
784
|
+
triggerPrefetch();
|
|
785
|
+
observer.disconnect();
|
|
786
|
+
break;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}, { threshold: 0 });
|
|
790
|
+
observer.observe(element);
|
|
791
|
+
return () => {
|
|
792
|
+
observer.disconnect();
|
|
793
|
+
};
|
|
794
|
+
}, [prefetchStrategy, triggerPrefetch, resolvedRef]);
|
|
795
|
+
return /* @__PURE__ */ jsxDEV("a", {
|
|
796
|
+
...rest,
|
|
797
|
+
ref: resolvedRef,
|
|
798
|
+
href: to,
|
|
799
|
+
onClick: handleClick,
|
|
800
|
+
onMouseEnter: handleMouseEnter,
|
|
801
|
+
onFocus: handleFocus,
|
|
802
|
+
children
|
|
803
|
+
}, undefined, false, undefined, this);
|
|
804
|
+
});
|
|
805
|
+
var NavLink = React.forwardRef(function NavLink2({ className, style, end = false, to, ...rest }, ref) {
|
|
806
|
+
const [navigationState, setNavigationState] = React.useState(() => {
|
|
807
|
+
if (typeof window !== "undefined") {
|
|
808
|
+
return router.getState();
|
|
809
|
+
}
|
|
810
|
+
return { pathname: "/", search: "", hash: "" };
|
|
811
|
+
});
|
|
812
|
+
React.useEffect(() => {
|
|
813
|
+
const unsubscribe = onNavigate((event) => {
|
|
814
|
+
setNavigationState(event.to);
|
|
815
|
+
});
|
|
816
|
+
return unsubscribe;
|
|
817
|
+
}, []);
|
|
818
|
+
const isActive = React.useMemo(() => {
|
|
819
|
+
const toPath = to.split("?")[0].split("#")[0];
|
|
820
|
+
if (end) {
|
|
821
|
+
return navigationState.pathname === toPath;
|
|
822
|
+
}
|
|
823
|
+
return navigationState.pathname.startsWith(toPath) && (toPath === "/" ? navigationState.pathname === "/" : true);
|
|
824
|
+
}, [to, end, navigationState.pathname]);
|
|
825
|
+
const isPending = false;
|
|
826
|
+
const activeProps = { isActive, isPending };
|
|
827
|
+
const resolvedClassName = typeof className === "function" ? className(activeProps) : className;
|
|
828
|
+
const resolvedStyle = typeof style === "function" ? style(activeProps) : style;
|
|
829
|
+
return /* @__PURE__ */ jsxDEV(Link, {
|
|
830
|
+
...rest,
|
|
831
|
+
ref,
|
|
832
|
+
to,
|
|
833
|
+
className: resolvedClassName,
|
|
834
|
+
style: resolvedStyle,
|
|
835
|
+
"aria-current": isActive ? "page" : undefined
|
|
836
|
+
}, undefined, false, undefined, this);
|
|
837
|
+
});
|
|
838
|
+
function useIsActive(path, end = false) {
|
|
839
|
+
const [pathname, setPathname] = React.useState(() => {
|
|
840
|
+
if (typeof window !== "undefined") {
|
|
841
|
+
return window.location.pathname;
|
|
842
|
+
}
|
|
843
|
+
return "/";
|
|
844
|
+
});
|
|
845
|
+
React.useEffect(() => {
|
|
846
|
+
const unsubscribe = onNavigate((event) => {
|
|
847
|
+
setPathname(event.to.pathname);
|
|
848
|
+
});
|
|
849
|
+
return unsubscribe;
|
|
850
|
+
}, []);
|
|
851
|
+
const toPath = path.split("?")[0].split("#")[0];
|
|
852
|
+
if (end) {
|
|
853
|
+
return pathname === toPath;
|
|
854
|
+
}
|
|
855
|
+
return pathname.startsWith(toPath) && (toPath === "/" ? pathname === "/" : true);
|
|
856
|
+
}
|
|
857
|
+
// src/typed-link.tsx
|
|
858
|
+
import * as React2 from "react";
|
|
859
|
+
import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
|
|
860
|
+
function buildPathWithParams(pattern, params) {
|
|
861
|
+
if (!params)
|
|
862
|
+
return pattern;
|
|
863
|
+
let result = pattern;
|
|
864
|
+
for (const [key, value] of Object.entries(params)) {
|
|
865
|
+
if (value === undefined)
|
|
866
|
+
continue;
|
|
867
|
+
if (result.includes(`[...${key}]`)) {
|
|
868
|
+
const arrayValue = Array.isArray(value) ? value : [value];
|
|
869
|
+
result = result.replace(`[...${key}]`, arrayValue.join("/"));
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
if (result.includes(`[[${key}]]`)) {
|
|
873
|
+
result = result.replace(`[[${key}]]`, Array.isArray(value) ? value[0] : value);
|
|
874
|
+
continue;
|
|
875
|
+
}
|
|
876
|
+
if (result.includes(`[${key}]`)) {
|
|
877
|
+
result = result.replace(`[${key}]`, Array.isArray(value) ? value[0] : value);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
result = result.replace(/\/?\[\[[^\]]+\]\]/g, "");
|
|
881
|
+
return result;
|
|
882
|
+
}
|
|
883
|
+
function buildSearchString(search) {
|
|
884
|
+
if (!search)
|
|
885
|
+
return "";
|
|
886
|
+
const params = new URLSearchParams;
|
|
887
|
+
for (const [key, value] of Object.entries(search)) {
|
|
888
|
+
if (value === undefined || value === null)
|
|
889
|
+
continue;
|
|
890
|
+
if (Array.isArray(value)) {
|
|
891
|
+
for (const v of value) {
|
|
892
|
+
params.append(key, String(v));
|
|
893
|
+
}
|
|
894
|
+
} else {
|
|
895
|
+
params.set(key, String(value));
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
const queryString = params.toString();
|
|
899
|
+
return queryString ? `?${queryString}` : "";
|
|
900
|
+
}
|
|
901
|
+
function buildHashString(hash) {
|
|
902
|
+
if (!hash)
|
|
903
|
+
return "";
|
|
904
|
+
const params = new URLSearchParams;
|
|
905
|
+
for (const [key, value] of Object.entries(hash)) {
|
|
906
|
+
if (value === undefined || value === null)
|
|
907
|
+
continue;
|
|
908
|
+
params.set(key, String(value));
|
|
909
|
+
}
|
|
910
|
+
const hashString = params.toString();
|
|
911
|
+
return hashString ? `#${hashString}` : "";
|
|
912
|
+
}
|
|
913
|
+
function buildUrl(pattern, options = {}) {
|
|
914
|
+
const { params, search, hash } = options;
|
|
915
|
+
const path = buildPathWithParams(pattern, params);
|
|
916
|
+
const searchString = buildSearchString(search);
|
|
917
|
+
const hashString = buildHashString(hash);
|
|
918
|
+
return `${path}${searchString}${hashString}`;
|
|
919
|
+
}
|
|
920
|
+
function isExternalUrl2(url) {
|
|
921
|
+
if (typeof window === "undefined")
|
|
922
|
+
return false;
|
|
923
|
+
if (url.startsWith("/") && !url.startsWith("//") || url.startsWith(".")) {
|
|
924
|
+
return false;
|
|
925
|
+
}
|
|
926
|
+
try {
|
|
927
|
+
const parsed = new URL(url, window.location.origin);
|
|
928
|
+
return parsed.origin !== window.location.origin;
|
|
929
|
+
} catch {
|
|
930
|
+
return false;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
function shouldNavigate2(event) {
|
|
934
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
|
935
|
+
return false;
|
|
936
|
+
}
|
|
937
|
+
if (event.button !== 0) {
|
|
938
|
+
return false;
|
|
939
|
+
}
|
|
940
|
+
if (event.defaultPrevented) {
|
|
941
|
+
return false;
|
|
942
|
+
}
|
|
943
|
+
return true;
|
|
944
|
+
}
|
|
945
|
+
var TypedLink = React2.forwardRef(function TypedLink2(props, ref) {
|
|
946
|
+
const {
|
|
947
|
+
to,
|
|
948
|
+
params,
|
|
949
|
+
search,
|
|
950
|
+
hash,
|
|
951
|
+
prefetch: prefetchStrategy = "intent",
|
|
952
|
+
replace = false,
|
|
953
|
+
preventScrollReset = false,
|
|
954
|
+
state,
|
|
955
|
+
reloadDocument = false,
|
|
956
|
+
onClick,
|
|
957
|
+
onMouseEnter,
|
|
958
|
+
onFocus,
|
|
959
|
+
children,
|
|
960
|
+
...rest
|
|
961
|
+
} = props;
|
|
962
|
+
const internalRef = React2.useRef(null);
|
|
963
|
+
const resolvedRef = ref || internalRef;
|
|
964
|
+
const hasPrefetched = React2.useRef(false);
|
|
965
|
+
const href = React2.useMemo(() => {
|
|
966
|
+
return buildUrl(to, {
|
|
967
|
+
params,
|
|
968
|
+
search,
|
|
969
|
+
hash
|
|
970
|
+
});
|
|
971
|
+
}, [to, params, search, hash]);
|
|
972
|
+
const isExternal = isExternalUrl2(href);
|
|
973
|
+
const triggerPrefetch = React2.useCallback(() => {
|
|
974
|
+
if (hasPrefetched.current || isExternal || prefetchStrategy === "none") {
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
hasPrefetched.current = true;
|
|
978
|
+
prefetch(href);
|
|
979
|
+
}, [href, isExternal, prefetchStrategy]);
|
|
980
|
+
const handleClick = React2.useCallback((event) => {
|
|
981
|
+
onClick?.(event);
|
|
982
|
+
if (isExternal || reloadDocument) {
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
if (!shouldNavigate2(event)) {
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
const target = event.currentTarget.getAttribute("target");
|
|
989
|
+
if (target && target !== "_self") {
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
event.preventDefault();
|
|
993
|
+
navigate(href, { replace, state });
|
|
994
|
+
if (!preventScrollReset && typeof window !== "undefined") {
|
|
995
|
+
window.scrollTo(0, 0);
|
|
996
|
+
}
|
|
997
|
+
}, [onClick, href, replace, state, isExternal, reloadDocument, preventScrollReset]);
|
|
998
|
+
const handleMouseEnter = React2.useCallback((event) => {
|
|
999
|
+
onMouseEnter?.(event);
|
|
1000
|
+
if (prefetchStrategy === "intent") {
|
|
1001
|
+
triggerPrefetch();
|
|
1002
|
+
}
|
|
1003
|
+
}, [onMouseEnter, prefetchStrategy, triggerPrefetch]);
|
|
1004
|
+
const handleFocus = React2.useCallback((event) => {
|
|
1005
|
+
onFocus?.(event);
|
|
1006
|
+
if (prefetchStrategy === "intent") {
|
|
1007
|
+
triggerPrefetch();
|
|
1008
|
+
}
|
|
1009
|
+
}, [onFocus, prefetchStrategy, triggerPrefetch]);
|
|
1010
|
+
React2.useEffect(() => {
|
|
1011
|
+
if (prefetchStrategy === "render") {
|
|
1012
|
+
triggerPrefetch();
|
|
1013
|
+
}
|
|
1014
|
+
}, [prefetchStrategy, triggerPrefetch]);
|
|
1015
|
+
React2.useEffect(() => {
|
|
1016
|
+
if (prefetchStrategy !== "viewport" || typeof IntersectionObserver === "undefined") {
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
const element = resolvedRef.current;
|
|
1020
|
+
if (!element)
|
|
1021
|
+
return;
|
|
1022
|
+
const observer = new IntersectionObserver((entries) => {
|
|
1023
|
+
for (const entry of entries) {
|
|
1024
|
+
if (entry.isIntersecting) {
|
|
1025
|
+
triggerPrefetch();
|
|
1026
|
+
observer.disconnect();
|
|
1027
|
+
break;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}, { threshold: 0 });
|
|
1031
|
+
observer.observe(element);
|
|
1032
|
+
return () => {
|
|
1033
|
+
observer.disconnect();
|
|
1034
|
+
};
|
|
1035
|
+
}, [prefetchStrategy, triggerPrefetch, resolvedRef]);
|
|
1036
|
+
return /* @__PURE__ */ jsxDEV2("a", {
|
|
1037
|
+
...rest,
|
|
1038
|
+
ref: resolvedRef,
|
|
1039
|
+
href,
|
|
1040
|
+
onClick: handleClick,
|
|
1041
|
+
onMouseEnter: handleMouseEnter,
|
|
1042
|
+
onFocus: handleFocus,
|
|
1043
|
+
children
|
|
1044
|
+
}, undefined, false, undefined, this);
|
|
1045
|
+
});
|
|
1046
|
+
var TypedNavLink = React2.forwardRef(function TypedNavLink2(props, ref) {
|
|
1047
|
+
const {
|
|
1048
|
+
className,
|
|
1049
|
+
style,
|
|
1050
|
+
end = false,
|
|
1051
|
+
to,
|
|
1052
|
+
params,
|
|
1053
|
+
search,
|
|
1054
|
+
hash,
|
|
1055
|
+
...rest
|
|
1056
|
+
} = props;
|
|
1057
|
+
const [navigationState, setNavigationState] = React2.useState(() => {
|
|
1058
|
+
if (typeof window !== "undefined") {
|
|
1059
|
+
return router.getState();
|
|
1060
|
+
}
|
|
1061
|
+
return { pathname: "/", search: "", hash: "" };
|
|
1062
|
+
});
|
|
1063
|
+
React2.useEffect(() => {
|
|
1064
|
+
const unsubscribe = onNavigate((event) => {
|
|
1065
|
+
setNavigationState(event.to);
|
|
1066
|
+
});
|
|
1067
|
+
return unsubscribe;
|
|
1068
|
+
}, []);
|
|
1069
|
+
const targetPath = React2.useMemo(() => {
|
|
1070
|
+
return buildPathWithParams(to, params).split("?")[0].split("#")[0];
|
|
1071
|
+
}, [to, params]);
|
|
1072
|
+
const isActive = React2.useMemo(() => {
|
|
1073
|
+
if (end) {
|
|
1074
|
+
return navigationState.pathname === targetPath;
|
|
1075
|
+
}
|
|
1076
|
+
return navigationState.pathname.startsWith(targetPath) && (targetPath === "/" ? navigationState.pathname === "/" : true);
|
|
1077
|
+
}, [targetPath, end, navigationState.pathname]);
|
|
1078
|
+
const isPending = false;
|
|
1079
|
+
const activeProps = { isActive, isPending };
|
|
1080
|
+
const resolvedClassName = typeof className === "function" ? className(activeProps) : className;
|
|
1081
|
+
const resolvedStyle = typeof style === "function" ? style(activeProps) : style;
|
|
1082
|
+
const href = React2.useMemo(() => {
|
|
1083
|
+
return buildUrl(to, {
|
|
1084
|
+
params,
|
|
1085
|
+
search,
|
|
1086
|
+
hash
|
|
1087
|
+
});
|
|
1088
|
+
}, [to, params, search, hash]);
|
|
1089
|
+
return /* @__PURE__ */ jsxDEV2("a", {
|
|
1090
|
+
...rest,
|
|
1091
|
+
ref,
|
|
1092
|
+
href,
|
|
1093
|
+
className: resolvedClassName,
|
|
1094
|
+
style: resolvedStyle,
|
|
1095
|
+
"aria-current": isActive ? "page" : undefined
|
|
1096
|
+
}, undefined, false, undefined, this);
|
|
1097
|
+
});
|
|
1098
|
+
function useIsRouteActive(path, options = {}) {
|
|
1099
|
+
const { params, end = false } = options;
|
|
1100
|
+
const [pathname, setPathname] = React2.useState(() => {
|
|
1101
|
+
if (typeof window !== "undefined") {
|
|
1102
|
+
return window.location.pathname;
|
|
1103
|
+
}
|
|
1104
|
+
return "/";
|
|
1105
|
+
});
|
|
1106
|
+
React2.useEffect(() => {
|
|
1107
|
+
const unsubscribe = onNavigate((event) => {
|
|
1108
|
+
setPathname(event.to.pathname);
|
|
1109
|
+
});
|
|
1110
|
+
return unsubscribe;
|
|
1111
|
+
}, []);
|
|
1112
|
+
const targetPath = buildPathWithParams(path, params).split("?")[0].split("#")[0];
|
|
1113
|
+
if (end) {
|
|
1114
|
+
return pathname === targetPath;
|
|
1115
|
+
}
|
|
1116
|
+
return pathname.startsWith(targetPath) && (targetPath === "/" ? pathname === "/" : true);
|
|
1117
|
+
}
|
|
1118
|
+
// src/typed-navigate.ts
|
|
1119
|
+
function buildPathWithParams2(pattern, params) {
|
|
1120
|
+
if (!params)
|
|
1121
|
+
return pattern;
|
|
1122
|
+
let result = pattern;
|
|
1123
|
+
for (const [key, value] of Object.entries(params)) {
|
|
1124
|
+
if (value === undefined)
|
|
1125
|
+
continue;
|
|
1126
|
+
if (result.includes(`[...${key}]`)) {
|
|
1127
|
+
const arrayValue = Array.isArray(value) ? value : [value];
|
|
1128
|
+
result = result.replace(`[...${key}]`, arrayValue.join("/"));
|
|
1129
|
+
continue;
|
|
1130
|
+
}
|
|
1131
|
+
if (result.includes(`[[${key}]]`)) {
|
|
1132
|
+
result = result.replace(`[[${key}]]`, Array.isArray(value) ? value[0] : value);
|
|
1133
|
+
continue;
|
|
1134
|
+
}
|
|
1135
|
+
if (result.includes(`[${key}]`)) {
|
|
1136
|
+
result = result.replace(`[${key}]`, Array.isArray(value) ? value[0] : value);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
result = result.replace(/\/?\[\[[^\]]+\]\]/g, "");
|
|
1140
|
+
return result;
|
|
1141
|
+
}
|
|
1142
|
+
function buildSearchString2(search) {
|
|
1143
|
+
if (!search)
|
|
1144
|
+
return "";
|
|
1145
|
+
const params = new URLSearchParams;
|
|
1146
|
+
for (const [key, value] of Object.entries(search)) {
|
|
1147
|
+
if (value === undefined || value === null)
|
|
1148
|
+
continue;
|
|
1149
|
+
if (Array.isArray(value)) {
|
|
1150
|
+
for (const v of value) {
|
|
1151
|
+
params.append(key, String(v));
|
|
1152
|
+
}
|
|
1153
|
+
} else {
|
|
1154
|
+
params.set(key, String(value));
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
const queryString = params.toString();
|
|
1158
|
+
return queryString ? `?${queryString}` : "";
|
|
1159
|
+
}
|
|
1160
|
+
function buildHashString2(hash) {
|
|
1161
|
+
if (!hash)
|
|
1162
|
+
return "";
|
|
1163
|
+
const params = new URLSearchParams;
|
|
1164
|
+
for (const [key, value] of Object.entries(hash)) {
|
|
1165
|
+
if (value === undefined || value === null)
|
|
1166
|
+
continue;
|
|
1167
|
+
params.set(key, String(value));
|
|
1168
|
+
}
|
|
1169
|
+
const hashString = params.toString();
|
|
1170
|
+
return hashString ? `#${hashString}` : "";
|
|
1171
|
+
}
|
|
1172
|
+
function buildTypedUrl(pattern, options = {}) {
|
|
1173
|
+
const { params, search, hash } = options;
|
|
1174
|
+
const path = buildPathWithParams2(pattern, params);
|
|
1175
|
+
const searchString = buildSearchString2(search);
|
|
1176
|
+
const hashString = buildHashString2(hash);
|
|
1177
|
+
return `${path}${searchString}${hashString}`;
|
|
1178
|
+
}
|
|
1179
|
+
async function typedNavigate(path, options) {
|
|
1180
|
+
const url = buildTypedUrl(path, {
|
|
1181
|
+
params: options?.params,
|
|
1182
|
+
search: options?.search,
|
|
1183
|
+
hash: options?.hash
|
|
1184
|
+
});
|
|
1185
|
+
await navigate(url, {
|
|
1186
|
+
replace: options?.replace,
|
|
1187
|
+
state: options?.state
|
|
1188
|
+
});
|
|
1189
|
+
if (options?.scroll !== false && typeof window !== "undefined") {
|
|
1190
|
+
window.scrollTo(0, 0);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
function useTypedNavigate() {
|
|
1194
|
+
const navigateFunction = (pathOrDelta, options) => {
|
|
1195
|
+
if (typeof pathOrDelta === "number") {
|
|
1196
|
+
router.go(pathOrDelta);
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
return typedNavigate(pathOrDelta, options);
|
|
1200
|
+
};
|
|
1201
|
+
return navigateFunction;
|
|
1202
|
+
}
|
|
1203
|
+
function typedRedirect(path, options) {
|
|
1204
|
+
const url = buildTypedUrl(path, {
|
|
1205
|
+
params: options?.params,
|
|
1206
|
+
search: options?.search,
|
|
1207
|
+
hash: options?.hash
|
|
1208
|
+
});
|
|
1209
|
+
const status = options?.status ?? 302;
|
|
1210
|
+
return new Response(null, {
|
|
1211
|
+
status,
|
|
1212
|
+
headers: {
|
|
1213
|
+
Location: url,
|
|
1214
|
+
...options?.headers instanceof Headers ? Object.fromEntries(options.headers.entries()) : options?.headers
|
|
1215
|
+
}
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
var redirect = typedRedirect;
|
|
1219
|
+
function parseTypedSearchParams(url) {
|
|
1220
|
+
const urlObj = typeof url === "string" ? new URL(url) : url;
|
|
1221
|
+
const result = {};
|
|
1222
|
+
urlObj.searchParams.forEach((value, key) => {
|
|
1223
|
+
if (result[key]) {
|
|
1224
|
+
const existing = result[key];
|
|
1225
|
+
result[key] = Array.isArray(existing) ? [...existing, value] : [existing, value];
|
|
1226
|
+
} else {
|
|
1227
|
+
result[key] = value;
|
|
1228
|
+
}
|
|
1229
|
+
});
|
|
1230
|
+
return result;
|
|
1231
|
+
}
|
|
1232
|
+
function parseTypedHashParams(url) {
|
|
1233
|
+
const urlObj = typeof url === "string" ? new URL(url) : url;
|
|
1234
|
+
if (!urlObj.hash) {
|
|
1235
|
+
return {};
|
|
1236
|
+
}
|
|
1237
|
+
const hashParams = new URLSearchParams(urlObj.hash.slice(1));
|
|
1238
|
+
const result = {};
|
|
1239
|
+
hashParams.forEach((value, key) => {
|
|
1240
|
+
result[key] = value;
|
|
1241
|
+
});
|
|
1242
|
+
return result;
|
|
1243
|
+
}
|
|
1244
|
+
function goBack2() {
|
|
1245
|
+
router.back();
|
|
1246
|
+
}
|
|
1247
|
+
function goForward2() {
|
|
1248
|
+
router.forward();
|
|
1249
|
+
}
|
|
1250
|
+
function go(delta) {
|
|
1251
|
+
router.go(delta);
|
|
1252
|
+
}
|
|
1253
|
+
function isCurrentPath(path, options) {
|
|
1254
|
+
if (typeof window === "undefined")
|
|
1255
|
+
return false;
|
|
1256
|
+
const targetPath = buildPathWithParams2(path, options?.params).split("?")[0].split("#")[0];
|
|
1257
|
+
const currentPath = window.location.pathname;
|
|
1258
|
+
if (options?.exact) {
|
|
1259
|
+
return currentPath === targetPath;
|
|
1260
|
+
}
|
|
1261
|
+
return currentPath.startsWith(targetPath) && (targetPath === "/" ? currentPath === "/" : true);
|
|
1262
|
+
}
|
|
1263
|
+
async function preloadRoute(path, options) {
|
|
1264
|
+
const url = buildTypedUrl(path, {
|
|
1265
|
+
params: options?.params,
|
|
1266
|
+
search: options?.search
|
|
1267
|
+
});
|
|
1268
|
+
try {
|
|
1269
|
+
await fetch(url, {
|
|
1270
|
+
headers: {
|
|
1271
|
+
Accept: "application/json",
|
|
1272
|
+
"X-Ereo-Preload": "true"
|
|
1273
|
+
}
|
|
1274
|
+
});
|
|
1275
|
+
} catch {}
|
|
1276
|
+
}
|
|
1277
|
+
// src/form.ts
|
|
1278
|
+
import { createElement as createElement2, useCallback as useCallback4, useRef as useRef3, useState as useState4, useEffect as useEffect3, useContext as useContext2, createContext as createContext2 } from "react";
|
|
1279
|
+
var FormContext = createContext2(null);
|
|
1280
|
+
function FormProvider({
|
|
1281
|
+
children,
|
|
1282
|
+
initialActionData
|
|
1283
|
+
}) {
|
|
1284
|
+
const [actionData, setActionData] = useState4(initialActionData);
|
|
1285
|
+
const [state, setState] = useState4("idle");
|
|
1286
|
+
return createElement2(FormContext.Provider, { value: { actionData, state, setActionData, setState } }, children);
|
|
1287
|
+
}
|
|
1288
|
+
function useFormContext() {
|
|
1289
|
+
return useContext2(FormContext);
|
|
1290
|
+
}
|
|
1291
|
+
function Form({
|
|
1292
|
+
method = "post",
|
|
1293
|
+
action,
|
|
1294
|
+
onSubmitStart,
|
|
1295
|
+
onSubmitEnd,
|
|
1296
|
+
replace = false,
|
|
1297
|
+
preventScrollReset = false,
|
|
1298
|
+
encType = "application/x-www-form-urlencoded",
|
|
1299
|
+
fetcherKey,
|
|
1300
|
+
children,
|
|
1301
|
+
onSubmit,
|
|
1302
|
+
...props
|
|
1303
|
+
}) {
|
|
1304
|
+
const formRef = useRef3(null);
|
|
1305
|
+
const formContext = useFormContext();
|
|
1306
|
+
const resolvedAction = action || (typeof window !== "undefined" ? window.location.pathname : "/");
|
|
1307
|
+
const handleSubmit = useCallback4(async (event) => {
|
|
1308
|
+
if (onSubmit) {
|
|
1309
|
+
onSubmit(event);
|
|
1310
|
+
if (event.defaultPrevented) {
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
if (typeof window === "undefined") {
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
event.preventDefault();
|
|
1318
|
+
const form = event.currentTarget;
|
|
1319
|
+
const formData = new FormData(form);
|
|
1320
|
+
if (formContext) {
|
|
1321
|
+
formContext.setState("submitting");
|
|
1322
|
+
}
|
|
1323
|
+
if (onSubmitStart) {
|
|
1324
|
+
onSubmitStart();
|
|
1325
|
+
}
|
|
1326
|
+
try {
|
|
1327
|
+
let body;
|
|
1328
|
+
if (encType === "application/x-www-form-urlencoded") {
|
|
1329
|
+
body = new URLSearchParams;
|
|
1330
|
+
formData.forEach((value, key) => {
|
|
1331
|
+
if (typeof value === "string") {
|
|
1332
|
+
body.append(key, value);
|
|
1333
|
+
}
|
|
1334
|
+
});
|
|
1335
|
+
} else {
|
|
1336
|
+
body = formData;
|
|
1337
|
+
}
|
|
1338
|
+
if (method.toLowerCase() === "get") {
|
|
1339
|
+
const url = new URL(resolvedAction, window.location.origin);
|
|
1340
|
+
const params = new URLSearchParams;
|
|
1341
|
+
formData.forEach((value, key) => {
|
|
1342
|
+
if (typeof value === "string") {
|
|
1343
|
+
params.append(key, value);
|
|
1344
|
+
}
|
|
1345
|
+
});
|
|
1346
|
+
url.search = params.toString();
|
|
1347
|
+
await router.navigate(url.pathname + url.search, { replace });
|
|
1348
|
+
const result2 = {
|
|
1349
|
+
status: 200,
|
|
1350
|
+
ok: true
|
|
1351
|
+
};
|
|
1352
|
+
if (formContext) {
|
|
1353
|
+
formContext.setState("idle");
|
|
1354
|
+
}
|
|
1355
|
+
if (onSubmitEnd) {
|
|
1356
|
+
onSubmitEnd(result2);
|
|
1357
|
+
}
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
const response = await fetch(resolvedAction, {
|
|
1361
|
+
method: method.toUpperCase(),
|
|
1362
|
+
body,
|
|
1363
|
+
headers: {
|
|
1364
|
+
Accept: "application/json",
|
|
1365
|
+
...encType === "application/x-www-form-urlencoded" && {
|
|
1366
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
});
|
|
1370
|
+
const result = {
|
|
1371
|
+
status: response.status,
|
|
1372
|
+
ok: response.ok
|
|
1373
|
+
};
|
|
1374
|
+
try {
|
|
1375
|
+
const data = await response.json();
|
|
1376
|
+
result.data = data;
|
|
1377
|
+
if (formContext) {
|
|
1378
|
+
formContext.setActionData(data);
|
|
1379
|
+
}
|
|
1380
|
+
} catch {}
|
|
1381
|
+
if (response.ok && !fetcherKey) {
|
|
1382
|
+
const redirectUrl = response.headers.get("X-Redirect-Url");
|
|
1383
|
+
if (redirectUrl) {
|
|
1384
|
+
await router.navigate(redirectUrl, { replace });
|
|
1385
|
+
} else if (!preventScrollReset && typeof window !== "undefined") {
|
|
1386
|
+
window.scrollTo(0, 0);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
if (formContext) {
|
|
1390
|
+
formContext.setState("idle");
|
|
1391
|
+
}
|
|
1392
|
+
if (onSubmitEnd) {
|
|
1393
|
+
onSubmitEnd(result);
|
|
1394
|
+
}
|
|
1395
|
+
} catch (error) {
|
|
1396
|
+
const result = {
|
|
1397
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
1398
|
+
status: 0,
|
|
1399
|
+
ok: false
|
|
1400
|
+
};
|
|
1401
|
+
if (formContext) {
|
|
1402
|
+
formContext.setState("error");
|
|
1403
|
+
}
|
|
1404
|
+
if (onSubmitEnd) {
|
|
1405
|
+
onSubmitEnd(result);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
}, [method, resolvedAction, onSubmitStart, onSubmitEnd, replace, preventScrollReset, encType, fetcherKey, formContext, onSubmit]);
|
|
1409
|
+
const formMethod = method.toLowerCase() === "get" ? "get" : "post";
|
|
1410
|
+
return createElement2("form", {
|
|
1411
|
+
ref: formRef,
|
|
1412
|
+
method: formMethod,
|
|
1413
|
+
action: resolvedAction,
|
|
1414
|
+
encType,
|
|
1415
|
+
onSubmit: handleSubmit,
|
|
1416
|
+
...props
|
|
1417
|
+
}, method !== "get" && method !== "post" ? createElement2("input", {
|
|
1418
|
+
type: "hidden",
|
|
1419
|
+
name: "_method",
|
|
1420
|
+
value: method.toUpperCase()
|
|
1421
|
+
}) : null, children);
|
|
1422
|
+
}
|
|
1423
|
+
function useSubmit() {
|
|
1424
|
+
const formContext = useFormContext();
|
|
1425
|
+
const submit = useCallback4(async (target, options = {}) => {
|
|
1426
|
+
const {
|
|
1427
|
+
method = "post",
|
|
1428
|
+
action,
|
|
1429
|
+
replace = false,
|
|
1430
|
+
preventScrollReset = false,
|
|
1431
|
+
encType = "application/x-www-form-urlencoded",
|
|
1432
|
+
fetcherKey
|
|
1433
|
+
} = options;
|
|
1434
|
+
if (typeof window === "undefined") {
|
|
1435
|
+
return { status: 0, ok: false, error: new Error("Not in browser environment") };
|
|
1436
|
+
}
|
|
1437
|
+
const resolvedAction = action || window.location.pathname;
|
|
1438
|
+
let formData;
|
|
1439
|
+
if (target instanceof HTMLFormElement) {
|
|
1440
|
+
formData = new FormData(target);
|
|
1441
|
+
} else if (target instanceof FormData) {
|
|
1442
|
+
formData = target;
|
|
1443
|
+
} else if (target instanceof URLSearchParams) {
|
|
1444
|
+
formData = new FormData;
|
|
1445
|
+
target.forEach((value, key) => {
|
|
1446
|
+
formData.append(key, value);
|
|
1447
|
+
});
|
|
1448
|
+
} else {
|
|
1449
|
+
formData = new FormData;
|
|
1450
|
+
for (const [key, value] of Object.entries(target)) {
|
|
1451
|
+
formData.append(key, value);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
if (formContext) {
|
|
1455
|
+
formContext.setState("submitting");
|
|
1456
|
+
}
|
|
1457
|
+
try {
|
|
1458
|
+
if (method.toLowerCase() === "get") {
|
|
1459
|
+
const url = new URL(resolvedAction, window.location.origin);
|
|
1460
|
+
const params = new URLSearchParams;
|
|
1461
|
+
formData.forEach((value, key) => {
|
|
1462
|
+
if (typeof value === "string") {
|
|
1463
|
+
params.append(key, value);
|
|
1464
|
+
}
|
|
1465
|
+
});
|
|
1466
|
+
url.search = params.toString();
|
|
1467
|
+
await router.navigate(url.pathname + url.search, { replace });
|
|
1468
|
+
if (formContext) {
|
|
1469
|
+
formContext.setState("idle");
|
|
1470
|
+
}
|
|
1471
|
+
return { status: 200, ok: true };
|
|
1472
|
+
}
|
|
1473
|
+
let body;
|
|
1474
|
+
if (encType === "application/x-www-form-urlencoded") {
|
|
1475
|
+
body = new URLSearchParams;
|
|
1476
|
+
formData.forEach((value, key) => {
|
|
1477
|
+
if (typeof value === "string") {
|
|
1478
|
+
body.append(key, value);
|
|
1479
|
+
}
|
|
1480
|
+
});
|
|
1481
|
+
} else {
|
|
1482
|
+
body = formData;
|
|
1483
|
+
}
|
|
1484
|
+
const response = await fetch(resolvedAction, {
|
|
1485
|
+
method: method.toUpperCase(),
|
|
1486
|
+
body,
|
|
1487
|
+
headers: {
|
|
1488
|
+
Accept: "application/json",
|
|
1489
|
+
...encType === "application/x-www-form-urlencoded" && {
|
|
1490
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
});
|
|
1494
|
+
const result = {
|
|
1495
|
+
status: response.status,
|
|
1496
|
+
ok: response.ok
|
|
1497
|
+
};
|
|
1498
|
+
try {
|
|
1499
|
+
const data = await response.json();
|
|
1500
|
+
result.data = data;
|
|
1501
|
+
if (formContext) {
|
|
1502
|
+
formContext.setActionData(data);
|
|
1503
|
+
}
|
|
1504
|
+
} catch {}
|
|
1505
|
+
if (response.ok && !fetcherKey) {
|
|
1506
|
+
const redirectUrl = response.headers.get("X-Redirect-Url");
|
|
1507
|
+
if (redirectUrl) {
|
|
1508
|
+
await router.navigate(redirectUrl, { replace });
|
|
1509
|
+
} else if (!preventScrollReset) {
|
|
1510
|
+
window.scrollTo(0, 0);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
if (formContext) {
|
|
1514
|
+
formContext.setState("idle");
|
|
1515
|
+
}
|
|
1516
|
+
return result;
|
|
1517
|
+
} catch (error) {
|
|
1518
|
+
const result = {
|
|
1519
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
1520
|
+
status: 0,
|
|
1521
|
+
ok: false
|
|
1522
|
+
};
|
|
1523
|
+
if (formContext) {
|
|
1524
|
+
formContext.setState("error");
|
|
1525
|
+
}
|
|
1526
|
+
return result;
|
|
1527
|
+
}
|
|
1528
|
+
}, [formContext]);
|
|
1529
|
+
return submit;
|
|
1530
|
+
}
|
|
1531
|
+
function useFetcher(key) {
|
|
1532
|
+
const [state, setStateInternal] = useState4("idle");
|
|
1533
|
+
const [data, setData] = useState4(undefined);
|
|
1534
|
+
const [error, setError] = useState4(undefined);
|
|
1535
|
+
const [formData, setFormData] = useState4(undefined);
|
|
1536
|
+
const [formMethod, setFormMethod] = useState4(undefined);
|
|
1537
|
+
const [formAction, setFormAction] = useState4(undefined);
|
|
1538
|
+
const mountedRef = useRef3(true);
|
|
1539
|
+
useEffect3(() => {
|
|
1540
|
+
mountedRef.current = true;
|
|
1541
|
+
return () => {
|
|
1542
|
+
mountedRef.current = false;
|
|
1543
|
+
};
|
|
1544
|
+
}, []);
|
|
1545
|
+
const safeSetState = useCallback4((newState) => {
|
|
1546
|
+
if (mountedRef.current) {
|
|
1547
|
+
setStateInternal(newState);
|
|
1548
|
+
}
|
|
1549
|
+
}, []);
|
|
1550
|
+
const reset = useCallback4(() => {
|
|
1551
|
+
if (mountedRef.current) {
|
|
1552
|
+
setStateInternal("idle");
|
|
1553
|
+
setData(undefined);
|
|
1554
|
+
setError(undefined);
|
|
1555
|
+
setFormData(undefined);
|
|
1556
|
+
setFormMethod(undefined);
|
|
1557
|
+
setFormAction(undefined);
|
|
1558
|
+
}
|
|
1559
|
+
}, []);
|
|
1560
|
+
const submit = useCallback4(async (target, options = {}) => {
|
|
1561
|
+
const {
|
|
1562
|
+
method = "post",
|
|
1563
|
+
action,
|
|
1564
|
+
encType = "application/x-www-form-urlencoded"
|
|
1565
|
+
} = options;
|
|
1566
|
+
if (typeof window === "undefined") {
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
const resolvedAction = action || window.location.pathname;
|
|
1570
|
+
let newFormData;
|
|
1571
|
+
if (target instanceof HTMLFormElement) {
|
|
1572
|
+
newFormData = new FormData(target);
|
|
1573
|
+
} else if (target instanceof FormData) {
|
|
1574
|
+
newFormData = target;
|
|
1575
|
+
} else if (target instanceof URLSearchParams) {
|
|
1576
|
+
newFormData = new FormData;
|
|
1577
|
+
target.forEach((value, key2) => {
|
|
1578
|
+
newFormData.append(key2, value);
|
|
1579
|
+
});
|
|
1580
|
+
} else {
|
|
1581
|
+
newFormData = new FormData;
|
|
1582
|
+
for (const [key2, value] of Object.entries(target)) {
|
|
1583
|
+
newFormData.append(key2, value);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
setFormData(newFormData);
|
|
1587
|
+
setFormMethod(method.toUpperCase());
|
|
1588
|
+
setFormAction(resolvedAction);
|
|
1589
|
+
safeSetState("submitting");
|
|
1590
|
+
try {
|
|
1591
|
+
if (method.toLowerCase() === "get") {
|
|
1592
|
+
const url = new URL(resolvedAction, window.location.origin);
|
|
1593
|
+
const params = new URLSearchParams;
|
|
1594
|
+
newFormData.forEach((value, key2) => {
|
|
1595
|
+
if (typeof value === "string") {
|
|
1596
|
+
params.append(key2, value);
|
|
1597
|
+
}
|
|
1598
|
+
});
|
|
1599
|
+
url.search = params.toString();
|
|
1600
|
+
const response2 = await fetch(url.toString(), {
|
|
1601
|
+
method: "GET",
|
|
1602
|
+
headers: { Accept: "application/json" }
|
|
1603
|
+
});
|
|
1604
|
+
if (mountedRef.current) {
|
|
1605
|
+
try {
|
|
1606
|
+
const responseData = await response2.json();
|
|
1607
|
+
setData(responseData);
|
|
1608
|
+
} catch {}
|
|
1609
|
+
safeSetState("idle");
|
|
1610
|
+
}
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
let body;
|
|
1614
|
+
if (encType === "application/x-www-form-urlencoded") {
|
|
1615
|
+
body = new URLSearchParams;
|
|
1616
|
+
newFormData.forEach((value, key2) => {
|
|
1617
|
+
if (typeof value === "string") {
|
|
1618
|
+
body.append(key2, value);
|
|
1619
|
+
}
|
|
1620
|
+
});
|
|
1621
|
+
} else {
|
|
1622
|
+
body = newFormData;
|
|
1623
|
+
}
|
|
1624
|
+
const response = await fetch(resolvedAction, {
|
|
1625
|
+
method: method.toUpperCase(),
|
|
1626
|
+
body,
|
|
1627
|
+
headers: {
|
|
1628
|
+
Accept: "application/json",
|
|
1629
|
+
...encType === "application/x-www-form-urlencoded" && {
|
|
1630
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
});
|
|
1634
|
+
if (mountedRef.current) {
|
|
1635
|
+
try {
|
|
1636
|
+
const responseData = await response.json();
|
|
1637
|
+
setData(responseData);
|
|
1638
|
+
} catch {}
|
|
1639
|
+
safeSetState("idle");
|
|
1640
|
+
}
|
|
1641
|
+
} catch (err) {
|
|
1642
|
+
if (mountedRef.current) {
|
|
1643
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
1644
|
+
safeSetState("error");
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
}, [safeSetState]);
|
|
1648
|
+
const load = useCallback4(async (href) => {
|
|
1649
|
+
if (typeof window === "undefined") {
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
setFormAction(href);
|
|
1653
|
+
setFormMethod("GET");
|
|
1654
|
+
safeSetState("loading");
|
|
1655
|
+
try {
|
|
1656
|
+
const response = await fetch(href, {
|
|
1657
|
+
method: "GET",
|
|
1658
|
+
headers: { Accept: "application/json" }
|
|
1659
|
+
});
|
|
1660
|
+
if (mountedRef.current) {
|
|
1661
|
+
try {
|
|
1662
|
+
const responseData = await response.json();
|
|
1663
|
+
setData(responseData);
|
|
1664
|
+
} catch {}
|
|
1665
|
+
safeSetState("idle");
|
|
1666
|
+
}
|
|
1667
|
+
} catch (err) {
|
|
1668
|
+
if (mountedRef.current) {
|
|
1669
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
1670
|
+
safeSetState("error");
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
}, [safeSetState]);
|
|
1674
|
+
const FetcherForm = useCallback4((formProps) => {
|
|
1675
|
+
return Form({
|
|
1676
|
+
...formProps,
|
|
1677
|
+
fetcherKey: key || "fetcher",
|
|
1678
|
+
onSubmitStart: () => {
|
|
1679
|
+
safeSetState("submitting");
|
|
1680
|
+
if (formProps.onSubmitStart) {
|
|
1681
|
+
formProps.onSubmitStart();
|
|
1682
|
+
}
|
|
1683
|
+
},
|
|
1684
|
+
onSubmitEnd: (result) => {
|
|
1685
|
+
if (mountedRef.current) {
|
|
1686
|
+
if (result.ok) {
|
|
1687
|
+
setData(result.data);
|
|
1688
|
+
safeSetState("idle");
|
|
1689
|
+
} else {
|
|
1690
|
+
setError(result.error);
|
|
1691
|
+
safeSetState("error");
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
if (formProps.onSubmitEnd) {
|
|
1695
|
+
formProps.onSubmitEnd(result);
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
});
|
|
1699
|
+
}, [key, safeSetState]);
|
|
1700
|
+
return {
|
|
1701
|
+
state,
|
|
1702
|
+
data,
|
|
1703
|
+
error,
|
|
1704
|
+
formData,
|
|
1705
|
+
formMethod,
|
|
1706
|
+
formAction,
|
|
1707
|
+
Form: FetcherForm,
|
|
1708
|
+
submit,
|
|
1709
|
+
load,
|
|
1710
|
+
reset
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
function useActionData2() {
|
|
1714
|
+
const context = useFormContext();
|
|
1715
|
+
return context?.actionData;
|
|
1716
|
+
}
|
|
1717
|
+
function useNavigation2() {
|
|
1718
|
+
const [navigationState, setNavigationState] = useState4(() => {
|
|
1719
|
+
if (typeof window === "undefined") {
|
|
1720
|
+
return { pathname: "/", search: "", hash: "" };
|
|
1721
|
+
}
|
|
1722
|
+
return router.getState();
|
|
1723
|
+
});
|
|
1724
|
+
const formContext = useFormContext();
|
|
1725
|
+
useEffect3(() => {
|
|
1726
|
+
return router.subscribe((event) => {
|
|
1727
|
+
setNavigationState(event.to);
|
|
1728
|
+
});
|
|
1729
|
+
}, []);
|
|
1730
|
+
return {
|
|
1731
|
+
...navigationState,
|
|
1732
|
+
state: formContext?.state || "idle"
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
function serializeFormData(formData) {
|
|
1736
|
+
const params = new URLSearchParams;
|
|
1737
|
+
formData.forEach((value, key) => {
|
|
1738
|
+
if (typeof value === "string") {
|
|
1739
|
+
params.append(key, value);
|
|
1740
|
+
}
|
|
1741
|
+
});
|
|
1742
|
+
return params.toString();
|
|
1743
|
+
}
|
|
1744
|
+
function parseFormData(data) {
|
|
1745
|
+
const formData = new FormData;
|
|
1746
|
+
const params = new URLSearchParams(data);
|
|
1747
|
+
params.forEach((value, key) => {
|
|
1748
|
+
formData.append(key, value);
|
|
1749
|
+
});
|
|
1750
|
+
return formData;
|
|
1751
|
+
}
|
|
1752
|
+
function formDataToObject(formData) {
|
|
1753
|
+
const result = {};
|
|
1754
|
+
formData.forEach((value, key) => {
|
|
1755
|
+
if (typeof value === "string") {
|
|
1756
|
+
if (key in result) {
|
|
1757
|
+
const existing = result[key];
|
|
1758
|
+
if (Array.isArray(existing)) {
|
|
1759
|
+
existing.push(value);
|
|
1760
|
+
} else {
|
|
1761
|
+
result[key] = [existing, value];
|
|
1762
|
+
}
|
|
1763
|
+
} else {
|
|
1764
|
+
result[key] = value;
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
});
|
|
1768
|
+
return result;
|
|
1769
|
+
}
|
|
1770
|
+
function objectToFormData(obj) {
|
|
1771
|
+
const formData = new FormData;
|
|
1772
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1773
|
+
if (Array.isArray(value)) {
|
|
1774
|
+
value.forEach((v) => formData.append(key, String(v)));
|
|
1775
|
+
} else {
|
|
1776
|
+
formData.append(key, String(value));
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
return formData;
|
|
1780
|
+
}
|
|
1781
|
+
// src/error-boundary.tsx
|
|
1782
|
+
import React3, {
|
|
1783
|
+
Component,
|
|
1784
|
+
useContext as useContext3
|
|
1785
|
+
} from "react";
|
|
1786
|
+
class ErrorBoundary extends Component {
|
|
1787
|
+
constructor(props) {
|
|
1788
|
+
super(props);
|
|
1789
|
+
this.state = {
|
|
1790
|
+
error: null,
|
|
1791
|
+
errorInfo: null
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
static getDerivedStateFromError(error) {
|
|
1795
|
+
return { error };
|
|
1796
|
+
}
|
|
1797
|
+
componentDidCatch(error, errorInfo) {
|
|
1798
|
+
this.setState({ errorInfo });
|
|
1799
|
+
if (this.props.onError) {
|
|
1800
|
+
this.props.onError(error, errorInfo);
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
reset = () => {
|
|
1804
|
+
this.setState({
|
|
1805
|
+
error: null,
|
|
1806
|
+
errorInfo: null
|
|
1807
|
+
});
|
|
1808
|
+
};
|
|
1809
|
+
render() {
|
|
1810
|
+
const { error } = this.state;
|
|
1811
|
+
const { fallback, children } = this.props;
|
|
1812
|
+
if (error) {
|
|
1813
|
+
if (typeof fallback === "function") {
|
|
1814
|
+
return fallback(error, this.reset);
|
|
1815
|
+
}
|
|
1816
|
+
if (fallback !== undefined) {
|
|
1817
|
+
return fallback;
|
|
1818
|
+
}
|
|
1819
|
+
return React3.createElement("div", {
|
|
1820
|
+
style: {
|
|
1821
|
+
padding: "20px",
|
|
1822
|
+
border: "1px solid #f5c6cb",
|
|
1823
|
+
borderRadius: "4px",
|
|
1824
|
+
backgroundColor: "#f8d7da",
|
|
1825
|
+
color: "#721c24"
|
|
1826
|
+
}
|
|
1827
|
+
}, React3.createElement("h2", null, "Something went wrong"), React3.createElement("p", null, error.message), React3.createElement("button", {
|
|
1828
|
+
onClick: this.reset,
|
|
1829
|
+
style: {
|
|
1830
|
+
padding: "8px 16px",
|
|
1831
|
+
backgroundColor: "#721c24",
|
|
1832
|
+
color: "white",
|
|
1833
|
+
border: "none",
|
|
1834
|
+
borderRadius: "4px",
|
|
1835
|
+
cursor: "pointer"
|
|
1836
|
+
}
|
|
1837
|
+
}, "Try again"));
|
|
1838
|
+
}
|
|
1839
|
+
return children;
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
function useErrorBoundary() {
|
|
1843
|
+
const context = useContext3(ErrorContext);
|
|
1844
|
+
if (!context) {
|
|
1845
|
+
throw new Error("useErrorBoundary must be used within an EreoProvider or ErrorProvider. " + "Make sure your component is wrapped with the appropriate provider.");
|
|
1846
|
+
}
|
|
1847
|
+
return {
|
|
1848
|
+
error: context.error,
|
|
1849
|
+
reset: context.clearError,
|
|
1850
|
+
showBoundary: (error) => context.setError(error)
|
|
1851
|
+
};
|
|
1852
|
+
}
|
|
1853
|
+
function useRouteError() {
|
|
1854
|
+
const context = useContext3(ErrorContext);
|
|
1855
|
+
return context?.error;
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
class RouteErrorBoundary extends Component {
|
|
1859
|
+
constructor(props) {
|
|
1860
|
+
super(props);
|
|
1861
|
+
this.state = {
|
|
1862
|
+
error: null,
|
|
1863
|
+
errorInfo: null,
|
|
1864
|
+
retryCount: 0
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
static getDerivedStateFromError(error) {
|
|
1868
|
+
return { error };
|
|
1869
|
+
}
|
|
1870
|
+
componentDidCatch(error, errorInfo) {
|
|
1871
|
+
this.setState({ errorInfo });
|
|
1872
|
+
const { errorConfig, routeId } = this.props;
|
|
1873
|
+
if (errorConfig?.reportError) {
|
|
1874
|
+
errorConfig.reportError(error, {
|
|
1875
|
+
route: routeId,
|
|
1876
|
+
phase: "render"
|
|
1877
|
+
});
|
|
1878
|
+
}
|
|
1879
|
+
if (errorConfig?.onError === "silent") {
|
|
1880
|
+
console.error(`[Route ${routeId}] Error:`, error);
|
|
1881
|
+
} else if (errorConfig?.onError === "redirect") {
|
|
1882
|
+
console.error(`[Route ${routeId}] Error (redirect mode):`, error);
|
|
1883
|
+
} else if (errorConfig?.onError === "toast") {
|
|
1884
|
+
console.error(`[Route ${routeId}] Error (toast mode):`, error);
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
reset = () => {
|
|
1888
|
+
const { errorConfig } = this.props;
|
|
1889
|
+
const { retryCount } = this.state;
|
|
1890
|
+
if (errorConfig?.retry && retryCount < errorConfig.retry.count) {
|
|
1891
|
+
setTimeout(() => {
|
|
1892
|
+
this.setState((prevState) => ({
|
|
1893
|
+
error: null,
|
|
1894
|
+
errorInfo: null,
|
|
1895
|
+
retryCount: prevState.retryCount + 1
|
|
1896
|
+
}));
|
|
1897
|
+
}, errorConfig.retry.delay);
|
|
1898
|
+
} else {
|
|
1899
|
+
this.setState({
|
|
1900
|
+
error: null,
|
|
1901
|
+
errorInfo: null,
|
|
1902
|
+
retryCount: 0
|
|
1903
|
+
});
|
|
1904
|
+
}
|
|
1905
|
+
};
|
|
1906
|
+
render() {
|
|
1907
|
+
const { error } = this.state;
|
|
1908
|
+
const { children, fallbackComponent: FallbackComponent, errorConfig, params = {} } = this.props;
|
|
1909
|
+
if (error) {
|
|
1910
|
+
if (errorConfig?.maxCaptures !== undefined && this.state.retryCount >= errorConfig.maxCaptures) {
|
|
1911
|
+
return React3.createElement("div", { style: { padding: "20px", color: "#721c24" } }, "Error limit exceeded. Please refresh the page.");
|
|
1912
|
+
}
|
|
1913
|
+
if (FallbackComponent) {
|
|
1914
|
+
return React3.createElement(FallbackComponent, { error, params });
|
|
1915
|
+
}
|
|
1916
|
+
if (errorConfig?.fallback) {
|
|
1917
|
+
return React3.createElement(errorConfig.fallback, {});
|
|
1918
|
+
}
|
|
1919
|
+
return React3.createElement("div", {
|
|
1920
|
+
style: {
|
|
1921
|
+
padding: "20px",
|
|
1922
|
+
border: "1px solid #f5c6cb",
|
|
1923
|
+
borderRadius: "4px",
|
|
1924
|
+
backgroundColor: "#f8d7da",
|
|
1925
|
+
color: "#721c24"
|
|
1926
|
+
}
|
|
1927
|
+
}, React3.createElement("h2", null, "Route Error"), React3.createElement("p", null, error.message), React3.createElement("button", {
|
|
1928
|
+
onClick: this.reset,
|
|
1929
|
+
style: {
|
|
1930
|
+
padding: "8px 16px",
|
|
1931
|
+
backgroundColor: "#721c24",
|
|
1932
|
+
color: "white",
|
|
1933
|
+
border: "none",
|
|
1934
|
+
borderRadius: "4px",
|
|
1935
|
+
cursor: "pointer"
|
|
1936
|
+
}
|
|
1937
|
+
}, "Retry"));
|
|
1938
|
+
}
|
|
1939
|
+
return children;
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
function isRouteErrorResponse(error) {
|
|
1943
|
+
return typeof error === "object" && error !== null && "status" in error && "statusText" in error && "data" in error && typeof error.status === "number" && typeof error.statusText === "string";
|
|
1944
|
+
}
|
|
1945
|
+
function createRouteErrorResponse(status, statusText, data = null) {
|
|
1946
|
+
return {
|
|
1947
|
+
status,
|
|
1948
|
+
statusText,
|
|
1949
|
+
data,
|
|
1950
|
+
internal: false
|
|
1951
|
+
};
|
|
1952
|
+
}
|
|
1953
|
+
function withErrorBoundary(WrappedComponent, errorBoundaryProps) {
|
|
1954
|
+
const displayName = WrappedComponent.displayName || WrappedComponent.name || "Component";
|
|
1955
|
+
const ComponentWithErrorBoundary = (props) => {
|
|
1956
|
+
const boundaryProps = {
|
|
1957
|
+
...errorBoundaryProps || {},
|
|
1958
|
+
children: React3.createElement(WrappedComponent, props)
|
|
1959
|
+
};
|
|
1960
|
+
return React3.createElement(ErrorBoundary, boundaryProps);
|
|
1961
|
+
};
|
|
1962
|
+
ComponentWithErrorBoundary.displayName = `withErrorBoundary(${displayName})`;
|
|
1963
|
+
return ComponentWithErrorBoundary;
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
class RouteError extends Error {
|
|
1967
|
+
status;
|
|
1968
|
+
statusText;
|
|
1969
|
+
data;
|
|
1970
|
+
constructor(status, statusText, data) {
|
|
1971
|
+
super(`${status} ${statusText}`);
|
|
1972
|
+
this.name = "RouteError";
|
|
1973
|
+
this.status = status;
|
|
1974
|
+
this.statusText = statusText;
|
|
1975
|
+
this.data = data;
|
|
1976
|
+
}
|
|
1977
|
+
toResponse() {
|
|
1978
|
+
return {
|
|
1979
|
+
status: this.status,
|
|
1980
|
+
statusText: this.statusText,
|
|
1981
|
+
data: this.data
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
// src/index.ts
|
|
1987
|
+
function initClient() {
|
|
1988
|
+
initializeIslands();
|
|
1989
|
+
setupScrollRestoration();
|
|
1990
|
+
setupAutoPrefetch({ strategy: "hover" });
|
|
1991
|
+
}
|
|
1992
|
+
export {
|
|
1993
|
+
withErrorBoundary,
|
|
1994
|
+
useTypedNavigate,
|
|
1995
|
+
useSubmit,
|
|
1996
|
+
useRouteError,
|
|
1997
|
+
useNavigationContext,
|
|
1998
|
+
useNavigation,
|
|
1999
|
+
useLoaderDataContext,
|
|
2000
|
+
useLoaderData,
|
|
2001
|
+
useIsRouteActive,
|
|
2002
|
+
useIsActive,
|
|
2003
|
+
useNavigation2 as useFormNavigation,
|
|
2004
|
+
useFormContext,
|
|
2005
|
+
useActionData2 as useFormActionData,
|
|
2006
|
+
useFetcher,
|
|
2007
|
+
useErrorContext,
|
|
2008
|
+
useErrorBoundary,
|
|
2009
|
+
useError,
|
|
2010
|
+
useActionDataContext,
|
|
2011
|
+
useActionData,
|
|
2012
|
+
typedRedirect,
|
|
2013
|
+
typedNavigate,
|
|
2014
|
+
goForward2 as typedGoForward,
|
|
2015
|
+
goBack2 as typedGoBack,
|
|
2016
|
+
submitAction,
|
|
2017
|
+
stripHydrationProps,
|
|
2018
|
+
shouldHydrate,
|
|
2019
|
+
setupScrollRestoration,
|
|
2020
|
+
setupLinkPrefetch,
|
|
2021
|
+
setupAutoPrefetch,
|
|
2022
|
+
serializeFormData,
|
|
2023
|
+
router,
|
|
2024
|
+
resetIslandCounter,
|
|
2025
|
+
registerIslandComponents,
|
|
2026
|
+
registerIslandComponent,
|
|
2027
|
+
redirect,
|
|
2028
|
+
preloadRoute,
|
|
2029
|
+
prefetchAll,
|
|
2030
|
+
prefetch,
|
|
2031
|
+
parseTypedSearchParams,
|
|
2032
|
+
parseTypedHashParams,
|
|
2033
|
+
parseHydrationDirective,
|
|
2034
|
+
parseFormData,
|
|
2035
|
+
onNavigate,
|
|
2036
|
+
objectToFormData,
|
|
2037
|
+
navigate,
|
|
2038
|
+
islandRegistry,
|
|
2039
|
+
isRouteErrorResponse,
|
|
2040
|
+
isPrefetching,
|
|
2041
|
+
isPrefetched,
|
|
2042
|
+
isCurrentPath,
|
|
2043
|
+
initializeIslands,
|
|
2044
|
+
initClient,
|
|
2045
|
+
hydrateIslands,
|
|
2046
|
+
goForward,
|
|
2047
|
+
goBack,
|
|
2048
|
+
go,
|
|
2049
|
+
getPrefetchedData,
|
|
2050
|
+
getNavigationState,
|
|
2051
|
+
getIslandCount,
|
|
2052
|
+
getIslandComponent,
|
|
2053
|
+
generateIslandId,
|
|
2054
|
+
formDataToObject,
|
|
2055
|
+
fetchLoaderData,
|
|
2056
|
+
createRouteErrorResponse,
|
|
2057
|
+
createIsland,
|
|
2058
|
+
createHydrationTrigger,
|
|
2059
|
+
clearPrefetchCache,
|
|
2060
|
+
cleanupIslands,
|
|
2061
|
+
buildUrl,
|
|
2062
|
+
buildTypedUrl,
|
|
2063
|
+
TypedNavLink,
|
|
2064
|
+
TypedLink,
|
|
2065
|
+
RouteErrorBoundary,
|
|
2066
|
+
RouteError,
|
|
2067
|
+
NavigationProvider,
|
|
2068
|
+
NavigationContext,
|
|
2069
|
+
NavLink,
|
|
2070
|
+
LoaderDataProvider,
|
|
2071
|
+
LoaderDataContext,
|
|
2072
|
+
Link,
|
|
2073
|
+
FormProvider,
|
|
2074
|
+
Form,
|
|
2075
|
+
ErrorProvider,
|
|
2076
|
+
ErrorContext,
|
|
2077
|
+
ErrorBoundary,
|
|
2078
|
+
EreoProvider,
|
|
2079
|
+
ActionDataProvider,
|
|
2080
|
+
ActionDataContext
|
|
2081
|
+
};
|