@real-router/solid 0.3.0 → 0.4.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 +11 -0
- package/dist/cjs/index.js +168 -65
- package/dist/esm/index.mjs +169 -66
- package/dist/types/RouterProvider.d.ts +1 -0
- package/dist/types/RouterProvider.d.ts.map +1 -1
- package/dist/types/components/Link.d.ts.map +1 -1
- package/dist/types/components/RouteView/components.d.ts.map +1 -1
- package/dist/types/components/RouteView/helpers.d.ts +2 -4
- package/dist/types/components/RouteView/helpers.d.ts.map +1 -1
- package/dist/types/components/RouterErrorBoundary.d.ts.map +1 -1
- package/dist/types/createSignalFromSource.d.ts.map +1 -1
- package/dist/types/createStoreFromSource.d.ts.map +1 -1
- package/dist/types/dom-utils/index.d.ts +1 -1
- package/dist/types/dom-utils/index.d.ts.map +1 -1
- package/dist/types/dom-utils/link-utils.d.ts +2 -1
- package/dist/types/dom-utils/link-utils.d.ts.map +1 -1
- package/dist/types/dom-utils/route-announcer.d.ts.map +1 -1
- package/dist/types/hooks/sharedNodeSource.d.ts +4 -0
- package/dist/types/hooks/sharedNodeSource.d.ts.map +1 -0
- package/dist/types/hooks/useRouteNode.d.ts.map +1 -1
- package/dist/types/hooks/useRouteNodeStore.d.ts.map +1 -1
- package/dist/types/hooks/useRouteStore.d.ts.map +1 -1
- package/dist/types/hooks/useRouterTransition.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/RouterProvider.tsx +1 -1
- package/src/components/Link.tsx +51 -8
- package/src/components/RouteView/RouteView.tsx +13 -13
- package/src/components/RouteView/components.tsx +12 -6
- package/src/components/RouteView/helpers.tsx +19 -17
- package/src/components/RouterErrorBoundary.tsx +4 -6
- package/src/createSignalFromSource.ts +9 -1
- package/src/createStoreFromSource.ts +1 -3
- package/src/hooks/sharedNodeSource.ts +30 -0
- package/src/hooks/useRouteNode.tsx +2 -4
- package/src/hooks/useRouteNodeStore.tsx +2 -3
- package/src/hooks/useRouteStore.tsx +0 -8
- package/src/hooks/useRouterTransition.tsx +15 -3
package/README.md
CHANGED
|
@@ -90,6 +90,17 @@ function UserProfile() {
|
|
|
90
90
|
|
|
91
91
|
Signal-based hooks (`useRoute`, `useRouteNode`) remain available for simpler use cases.
|
|
92
92
|
|
|
93
|
+
### Primitives
|
|
94
|
+
|
|
95
|
+
Two low-level bridges convert `@real-router/sources` `RouterSource<T>` instances into Solid reactive primitives. Use them when you build custom hooks on top of `@real-router/sources`:
|
|
96
|
+
|
|
97
|
+
| Primitive | Returns | Description |
|
|
98
|
+
| ------------------------ | ------------------ | ------------------------------------------------------------- |
|
|
99
|
+
| `createSignalFromSource` | `Accessor<T>` | Bridges a source to a Solid signal. Calls `onCleanup`. |
|
|
100
|
+
| `createStoreFromSource` | `T` (Solid store) | Bridges a source to a Solid store via `createStore + reconcile`. |
|
|
101
|
+
|
|
102
|
+
Both must be called inside a reactive owner (component body or `createRoot`).
|
|
103
|
+
|
|
93
104
|
```tsx
|
|
94
105
|
// useRouteNode — updates only when "users.*" changes
|
|
95
106
|
function UsersLayout() {
|
package/dist/cjs/index.js
CHANGED
|
@@ -8,8 +8,10 @@ var routeUtils = require('@real-router/route-utils');
|
|
|
8
8
|
var store = require('solid-js/store');
|
|
9
9
|
var core = require('@real-router/core');
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
// Local (non-global) Symbols — Symbol.for() would expose markers to spoofing
|
|
12
|
+
// via the global Symbol registry. See Gotchas section "RouteView Marker Objects".
|
|
13
|
+
const MATCH_MARKER = Symbol("RouteView.Match");
|
|
14
|
+
const NOT_FOUND_MARKER = Symbol("RouteView.NotFound");
|
|
13
15
|
function Match(props) {
|
|
14
16
|
const result = {
|
|
15
17
|
$$type: MATCH_MARKER,
|
|
@@ -20,6 +22,10 @@ function Match(props) {
|
|
|
20
22
|
return props.children;
|
|
21
23
|
}
|
|
22
24
|
};
|
|
25
|
+
|
|
26
|
+
// Marker object is identified by $$type Symbol in RouteView/helpers.tsx,
|
|
27
|
+
// not rendered as JSX. Cast required because JSX.Element does not include
|
|
28
|
+
// arbitrary marker shapes.
|
|
23
29
|
return result;
|
|
24
30
|
}
|
|
25
31
|
Match.displayName = "RouteView.Match";
|
|
@@ -30,6 +36,8 @@ function NotFound(props) {
|
|
|
30
36
|
return props.children;
|
|
31
37
|
}
|
|
32
38
|
};
|
|
39
|
+
|
|
40
|
+
// See Match for the marker-pattern rationale.
|
|
33
41
|
return result;
|
|
34
42
|
}
|
|
35
43
|
NotFound.displayName = "RouteView.NotFound";
|
|
@@ -69,43 +77,65 @@ function buildRenderList(elements, routeName, nodeName) {
|
|
|
69
77
|
notFoundChildren = child.children;
|
|
70
78
|
continue;
|
|
71
79
|
}
|
|
80
|
+
if (activeMatchFound) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
72
83
|
const {
|
|
73
84
|
segment,
|
|
74
85
|
exact,
|
|
75
86
|
fallback
|
|
76
87
|
} = child;
|
|
77
88
|
const fullSegmentName = nodeName ? `${nodeName}.${segment}` : segment;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
activeMatchFound = true;
|
|
81
|
-
const matchContent = child.children;
|
|
82
|
-
const content = fallback === undefined ? matchContent : web.createComponent(solidJs.Suspense, {
|
|
83
|
-
fallback: fallback,
|
|
84
|
-
children: matchContent
|
|
85
|
-
});
|
|
86
|
-
rendered.push(content);
|
|
89
|
+
if (!isSegmentMatch(routeName, fullSegmentName, exact)) {
|
|
90
|
+
continue;
|
|
87
91
|
}
|
|
92
|
+
activeMatchFound = true;
|
|
93
|
+
rendered.push(fallback === undefined ? child.children : web.createComponent(solidJs.Suspense, {
|
|
94
|
+
fallback: fallback,
|
|
95
|
+
get children() {
|
|
96
|
+
return child.children;
|
|
97
|
+
}
|
|
98
|
+
}));
|
|
88
99
|
}
|
|
89
100
|
if (!activeMatchFound && routeName === core.UNKNOWN_ROUTE && notFoundChildren !== null) {
|
|
90
101
|
rendered.push(notFoundChildren);
|
|
91
102
|
}
|
|
92
|
-
return
|
|
93
|
-
rendered,
|
|
94
|
-
activeMatchFound
|
|
95
|
-
};
|
|
103
|
+
return rendered;
|
|
96
104
|
}
|
|
97
105
|
|
|
98
106
|
function createSignalFromSource(source) {
|
|
99
107
|
const [value, setValue] = solidJs.createSignal(source.getSnapshot());
|
|
108
|
+
const sync = () => source.getSnapshot();
|
|
100
109
|
const unsubscribe = source.subscribe(() => {
|
|
101
|
-
setValue(
|
|
110
|
+
setValue(sync);
|
|
102
111
|
});
|
|
112
|
+
|
|
113
|
+
// Re-read after subscribe: lazy sources reconcile their snapshot in
|
|
114
|
+
// onFirstSubscribe (when reused after disconnect via cache). Listener is not
|
|
115
|
+
// notified for that internal update, so we must sync the signal manually.
|
|
116
|
+
// No-op when snapshot is unchanged (signal equality check).
|
|
117
|
+
setValue(sync);
|
|
103
118
|
solidJs.onCleanup(() => {
|
|
104
119
|
unsubscribe();
|
|
105
120
|
});
|
|
106
121
|
return value;
|
|
107
122
|
}
|
|
108
123
|
|
|
124
|
+
const cache$2 = new WeakMap();
|
|
125
|
+
function getOrCreateNodeSource(router, nodeName) {
|
|
126
|
+
let perRouter = cache$2.get(router);
|
|
127
|
+
if (!perRouter) {
|
|
128
|
+
perRouter = new Map();
|
|
129
|
+
cache$2.set(router, perRouter);
|
|
130
|
+
}
|
|
131
|
+
let source = perRouter.get(nodeName);
|
|
132
|
+
if (!source) {
|
|
133
|
+
source = sources.createRouteNodeSource(router, nodeName);
|
|
134
|
+
perRouter.set(nodeName, source);
|
|
135
|
+
}
|
|
136
|
+
return source;
|
|
137
|
+
}
|
|
138
|
+
|
|
109
139
|
const RouterContext = solidJs.createContext(null);
|
|
110
140
|
const RouteContext = solidJs.createContext(null);
|
|
111
141
|
|
|
@@ -119,27 +149,24 @@ const useRouter = () => {
|
|
|
119
149
|
|
|
120
150
|
function useRouteNode(nodeName) {
|
|
121
151
|
const router = useRouter();
|
|
122
|
-
|
|
123
|
-
return createSignalFromSource(store);
|
|
152
|
+
return createSignalFromSource(getOrCreateNodeSource(router, nodeName));
|
|
124
153
|
}
|
|
125
154
|
|
|
126
155
|
function RouteViewRoot(props) {
|
|
127
156
|
const routeState = useRouteNode(props.nodeName);
|
|
128
157
|
const resolved = solidJs.children(() => props.children);
|
|
158
|
+
const elements = solidJs.createMemo(() => {
|
|
159
|
+
const arr = [];
|
|
160
|
+
collectElements(resolved(), arr);
|
|
161
|
+
return arr;
|
|
162
|
+
});
|
|
129
163
|
return web.memo(() => {
|
|
130
164
|
const state = routeState();
|
|
131
165
|
if (!state.route) {
|
|
132
166
|
return null;
|
|
133
167
|
}
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
const {
|
|
137
|
-
rendered
|
|
138
|
-
} = buildRenderList(elements, state.route.name, props.nodeName);
|
|
139
|
-
if (rendered.length > 0) {
|
|
140
|
-
return rendered;
|
|
141
|
-
}
|
|
142
|
-
return null;
|
|
168
|
+
const rendered = buildRenderList(elements(), state.route.name, props.nodeName);
|
|
169
|
+
return rendered.length > 0 ? rendered : null;
|
|
143
170
|
});
|
|
144
171
|
}
|
|
145
172
|
RouteViewRoot.displayName = "RouteView";
|
|
@@ -165,14 +192,36 @@ const INTERNAL_ROUTE_PREFIX = "@@";
|
|
|
165
192
|
const VISUALLY_HIDDEN = "position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);clip-path:inset(50%);white-space:nowrap;border:0";
|
|
166
193
|
function createRouteAnnouncer(router, options) {
|
|
167
194
|
const prefix = "Navigated to ";
|
|
195
|
+
const getCustomText = options?.getAnnouncementText;
|
|
168
196
|
let isInitialNavigation = true;
|
|
169
197
|
let isReady = false;
|
|
170
198
|
let isDestroyed = false;
|
|
171
199
|
let lastAnnouncedText = "";
|
|
200
|
+
let pendingText = null;
|
|
172
201
|
let clearTimeoutId;
|
|
173
202
|
const announcer = getOrCreateAnnouncer();
|
|
203
|
+
const doAnnounce = (text, h1) => {
|
|
204
|
+
lastAnnouncedText = text;
|
|
205
|
+
clearTimeout(clearTimeoutId);
|
|
206
|
+
announcer.textContent = text;
|
|
207
|
+
clearTimeoutId = setTimeout(() => {
|
|
208
|
+
announcer.textContent = "";
|
|
209
|
+
lastAnnouncedText = "";
|
|
210
|
+
}, CLEAR_DELAY);
|
|
211
|
+
manageFocus(h1);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// Safari-ready delay: announcing before VoiceOver wires up the aria-live region
|
|
215
|
+
// causes the first announcement to be silently dropped. Wait SAFARI_READY_DELAY ms
|
|
216
|
+
// before marking the announcer "ready" — any navigation during that window is
|
|
217
|
+
// buffered in pendingText and flushed once the delay expires.
|
|
174
218
|
const safariTimeoutId = setTimeout(() => {
|
|
175
219
|
isReady = true;
|
|
220
|
+
if (pendingText !== null && !isDestroyed) {
|
|
221
|
+
const text = pendingText;
|
|
222
|
+
pendingText = null;
|
|
223
|
+
doAnnounce(text, document.querySelector("h1"));
|
|
224
|
+
}
|
|
176
225
|
}, SAFARI_READY_DELAY);
|
|
177
226
|
const unsubscribe = router.subscribe(({
|
|
178
227
|
route
|
|
@@ -181,22 +230,28 @@ function createRouteAnnouncer(router, options) {
|
|
|
181
230
|
isInitialNavigation = false;
|
|
182
231
|
return;
|
|
183
232
|
}
|
|
233
|
+
|
|
234
|
+
// Double rAF: waits for two paint frames so the incoming route's DOM
|
|
235
|
+
// (including the new <h1>) is fully rendered before resolveText reads it.
|
|
236
|
+
// Single rAF fires before the new route's template has been attached,
|
|
237
|
+
// which would cause resolveText to pick up the OLD h1 or fall back to
|
|
238
|
+
// document.title / route.name prematurely.
|
|
184
239
|
requestAnimationFrame(() => {
|
|
185
240
|
requestAnimationFrame(() => {
|
|
186
241
|
if (isDestroyed) {
|
|
187
242
|
return;
|
|
188
243
|
}
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
manageFocus();
|
|
244
|
+
const h1 = document.querySelector("h1");
|
|
245
|
+
const text = resolveText(route, prefix, getCustomText, h1);
|
|
246
|
+
if (!text || text === lastAnnouncedText) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (!isReady) {
|
|
250
|
+
// Defer announcement until Safari-ready window elapses (see safariTimeoutId).
|
|
251
|
+
pendingText = text;
|
|
252
|
+
return;
|
|
199
253
|
}
|
|
254
|
+
doAnnounce(text, h1);
|
|
200
255
|
});
|
|
201
256
|
});
|
|
202
257
|
});
|
|
@@ -226,15 +281,13 @@ function getOrCreateAnnouncer() {
|
|
|
226
281
|
function removeAnnouncer() {
|
|
227
282
|
document.querySelector(`[${ANNOUNCER_ATTR}]`)?.remove();
|
|
228
283
|
}
|
|
229
|
-
function resolveText(route, prefix, getCustomText) {
|
|
230
|
-
const h1 = document.querySelector("h1");
|
|
284
|
+
function resolveText(route, prefix, getCustomText, h1) {
|
|
231
285
|
const h1Text = h1?.textContent.trim() ?? "";
|
|
232
286
|
const routeName = route.name.startsWith(INTERNAL_ROUTE_PREFIX) ? "" : route.name;
|
|
233
287
|
const rawText = h1Text || document.title || routeName || globalThis.location.pathname;
|
|
234
288
|
return `${prefix}${rawText}`;
|
|
235
289
|
}
|
|
236
|
-
function manageFocus() {
|
|
237
|
-
const h1 = document.querySelector("h1");
|
|
290
|
+
function manageFocus(h1) {
|
|
238
291
|
if (!h1) {
|
|
239
292
|
return;
|
|
240
293
|
}
|
|
@@ -253,7 +306,10 @@ function buildHref(router, routeName, routeParams) {
|
|
|
253
306
|
try {
|
|
254
307
|
const buildUrl = router.buildUrl;
|
|
255
308
|
if (buildUrl) {
|
|
256
|
-
|
|
309
|
+
const url = buildUrl(routeName, routeParams);
|
|
310
|
+
if (url !== undefined) {
|
|
311
|
+
return url;
|
|
312
|
+
}
|
|
257
313
|
}
|
|
258
314
|
return router.buildPath(routeName, routeParams);
|
|
259
315
|
} catch {
|
|
@@ -261,25 +317,67 @@ function buildHref(router, routeName, routeParams) {
|
|
|
261
317
|
return undefined;
|
|
262
318
|
}
|
|
263
319
|
}
|
|
320
|
+
function parseTokens(value) {
|
|
321
|
+
return value ? value.match(/\S+/g) ?? [] : [];
|
|
322
|
+
}
|
|
264
323
|
function buildActiveClassName(isActive, activeClassName, baseClassName) {
|
|
265
324
|
if (isActive && activeClassName) {
|
|
266
|
-
|
|
325
|
+
const activeTokens = parseTokens(activeClassName);
|
|
326
|
+
if (activeTokens.length === 0) {
|
|
327
|
+
return baseClassName ?? undefined;
|
|
328
|
+
}
|
|
329
|
+
if (!baseClassName) {
|
|
330
|
+
return activeTokens.join(" ");
|
|
331
|
+
}
|
|
332
|
+
const baseTokens = parseTokens(baseClassName);
|
|
333
|
+
const seen = new Set(baseTokens);
|
|
334
|
+
for (const token of activeTokens) {
|
|
335
|
+
if (!seen.has(token)) {
|
|
336
|
+
seen.add(token);
|
|
337
|
+
baseTokens.push(token);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return baseTokens.join(" ");
|
|
267
341
|
}
|
|
268
342
|
return baseClassName ?? undefined;
|
|
269
343
|
}
|
|
270
344
|
function applyLinkA11y(element) {
|
|
345
|
+
if (!element) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
271
348
|
if (element instanceof HTMLAnchorElement || element instanceof HTMLButtonElement) {
|
|
272
349
|
return;
|
|
273
350
|
}
|
|
274
|
-
if (!element.
|
|
351
|
+
if (!element.hasAttribute("role")) {
|
|
275
352
|
element.setAttribute("role", "link");
|
|
276
353
|
}
|
|
277
|
-
if (!element.
|
|
354
|
+
if (!element.hasAttribute("tabindex")) {
|
|
278
355
|
element.setAttribute("tabindex", "0");
|
|
279
356
|
}
|
|
280
357
|
}
|
|
281
358
|
|
|
282
359
|
var _tmpl$ = /*#__PURE__*/web.template(`<a>`);
|
|
360
|
+
// Slow-path source cache: shared per-router, keyed by routeName + params + flags.
|
|
361
|
+
// Captured slow-path values are stable per Link (props captured at init), so the
|
|
362
|
+
// cache key is guaranteed stable for the lifetime of any consumer.
|
|
363
|
+
const activeSourceCache = new WeakMap();
|
|
364
|
+
function getOrCreateActiveSource(router, routeName, routeParams, activeStrict, ignoreQueryParams) {
|
|
365
|
+
let perRouter = activeSourceCache.get(router);
|
|
366
|
+
if (!perRouter) {
|
|
367
|
+
perRouter = new Map();
|
|
368
|
+
activeSourceCache.set(router, perRouter);
|
|
369
|
+
}
|
|
370
|
+
const key = `${routeName}|${JSON.stringify(routeParams)}|${activeStrict}|${ignoreQueryParams}`;
|
|
371
|
+
let source = perRouter.get(key);
|
|
372
|
+
if (!source) {
|
|
373
|
+
source = sources.createActiveRouteSource(router, routeName, routeParams, {
|
|
374
|
+
strict: activeStrict,
|
|
375
|
+
ignoreQueryParams
|
|
376
|
+
});
|
|
377
|
+
perRouter.set(key, source);
|
|
378
|
+
}
|
|
379
|
+
return source;
|
|
380
|
+
}
|
|
283
381
|
function Link(props) {
|
|
284
382
|
const merged = solidJs.mergeProps({
|
|
285
383
|
routeParams: EMPTY_PARAMS,
|
|
@@ -289,13 +387,13 @@ function Link(props) {
|
|
|
289
387
|
ignoreQueryParams: true
|
|
290
388
|
}, props);
|
|
291
389
|
const [local, rest] = solidJs.splitProps(merged, ["routeName", "routeParams", "routeOptions", "activeClassName", "activeStrict", "ignoreQueryParams", "onClick", "target", "class", "children"]);
|
|
292
|
-
const router = useRouter();
|
|
293
390
|
const ctx = solidJs.useContext(RouterContext);
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
391
|
+
if (!ctx) {
|
|
392
|
+
throw new Error("Link must be used within a RouterProvider");
|
|
393
|
+
}
|
|
394
|
+
const router = ctx.router;
|
|
395
|
+
const useFastPath = !local.activeStrict && local.ignoreQueryParams && local.routeParams === EMPTY_PARAMS;
|
|
396
|
+
const isActive = useFastPath ? () => ctx.routeSelector(local.routeName) : createSignalFromSource(getOrCreateActiveSource(router, local.routeName, local.routeParams, local.activeStrict, local.ignoreQueryParams));
|
|
299
397
|
const href = solidJs.createMemo(() => buildHref(router, local.routeName, local.routeParams));
|
|
300
398
|
const handleClick = evt => {
|
|
301
399
|
if (local.onClick) {
|
|
@@ -327,13 +425,13 @@ function Link(props) {
|
|
|
327
425
|
})();
|
|
328
426
|
}
|
|
329
427
|
|
|
330
|
-
const cache = new WeakMap();
|
|
428
|
+
const cache$1 = new WeakMap();
|
|
331
429
|
function useRouterError() {
|
|
332
430
|
const router = useRouter();
|
|
333
|
-
let source = cache.get(router);
|
|
431
|
+
let source = cache$1.get(router);
|
|
334
432
|
if (!source) {
|
|
335
433
|
source = sources.createErrorSource(router);
|
|
336
|
-
cache.set(router, source);
|
|
434
|
+
cache$1.set(router, source);
|
|
337
435
|
}
|
|
338
436
|
return createSignalFromSource(source);
|
|
339
437
|
}
|
|
@@ -352,9 +450,11 @@ function RouterErrorBoundary(props) {
|
|
|
352
450
|
return snap.version > dismissedVersion() ? snap.error : null;
|
|
353
451
|
});
|
|
354
452
|
const resetError = () => setDismissedVersion(snapshot().version);
|
|
355
|
-
return [web.memo(() => props.children), web.
|
|
356
|
-
|
|
357
|
-
|
|
453
|
+
return [web.memo(() => props.children), web.createComponent(solidJs.Show, {
|
|
454
|
+
get when() {
|
|
455
|
+
return visibleError();
|
|
456
|
+
},
|
|
457
|
+
children: error => props.fallback(error(), resetError)
|
|
358
458
|
})];
|
|
359
459
|
}
|
|
360
460
|
|
|
@@ -424,7 +524,9 @@ const useRoute = () => {
|
|
|
424
524
|
};
|
|
425
525
|
|
|
426
526
|
function createStoreFromSource(source) {
|
|
427
|
-
const [state, setState] = store.createStore(
|
|
527
|
+
const [state, setState] = store.createStore({
|
|
528
|
+
...source.getSnapshot()
|
|
529
|
+
});
|
|
428
530
|
const unsubscribe = source.subscribe(() => {
|
|
429
531
|
setState(store.reconcile(source.getSnapshot()));
|
|
430
532
|
});
|
|
@@ -433,23 +535,24 @@ function createStoreFromSource(source) {
|
|
|
433
535
|
}
|
|
434
536
|
|
|
435
537
|
function useRouteStore() {
|
|
436
|
-
const ctx = solidJs.useContext(RouteContext);
|
|
437
|
-
if (!ctx) {
|
|
438
|
-
throw new Error("useRouteStore must be used within a RouterProvider");
|
|
439
|
-
}
|
|
440
538
|
const router = useRouter();
|
|
441
539
|
return createStoreFromSource(sources.createRouteSource(router));
|
|
442
540
|
}
|
|
443
541
|
|
|
444
542
|
function useRouteNodeStore(nodeName) {
|
|
445
543
|
const router = useRouter();
|
|
446
|
-
return createStoreFromSource(
|
|
544
|
+
return createStoreFromSource(getOrCreateNodeSource(router, nodeName));
|
|
447
545
|
}
|
|
448
546
|
|
|
547
|
+
const cache = new WeakMap();
|
|
449
548
|
function useRouterTransition() {
|
|
450
549
|
const router = useRouter();
|
|
451
|
-
|
|
452
|
-
|
|
550
|
+
let source = cache.get(router);
|
|
551
|
+
if (!source) {
|
|
552
|
+
source = sources.createTransitionSource(router);
|
|
553
|
+
cache.set(router, source);
|
|
554
|
+
}
|
|
555
|
+
return createSignalFromSource(source);
|
|
453
556
|
}
|
|
454
557
|
|
|
455
558
|
function isRouteActive(linkRouteName, currentRouteName) {
|