@real-router/solid 0.3.1 → 0.4.1

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.
Files changed (34) hide show
  1. package/README.md +11 -0
  2. package/dist/cjs/index.js +124 -75
  3. package/dist/esm/index.mjs +126 -77
  4. package/dist/types/RouterProvider.d.ts +1 -0
  5. package/dist/types/RouterProvider.d.ts.map +1 -1
  6. package/dist/types/components/Link.d.ts.map +1 -1
  7. package/dist/types/components/RouteView/components.d.ts.map +1 -1
  8. package/dist/types/components/RouteView/helpers.d.ts +2 -4
  9. package/dist/types/components/RouteView/helpers.d.ts.map +1 -1
  10. package/dist/types/components/RouterErrorBoundary.d.ts.map +1 -1
  11. package/dist/types/createSignalFromSource.d.ts.map +1 -1
  12. package/dist/types/createStoreFromSource.d.ts.map +1 -1
  13. package/dist/types/dom-utils/index.d.ts +1 -1
  14. package/dist/types/dom-utils/index.d.ts.map +1 -1
  15. package/dist/types/dom-utils/link-utils.d.ts +2 -1
  16. package/dist/types/dom-utils/link-utils.d.ts.map +1 -1
  17. package/dist/types/dom-utils/route-announcer.d.ts.map +1 -1
  18. package/dist/types/hooks/useRouteNode.d.ts.map +1 -1
  19. package/dist/types/hooks/useRouteStore.d.ts.map +1 -1
  20. package/package.json +5 -5
  21. package/src/RouterProvider.tsx +1 -1
  22. package/src/components/Link.tsx +6 -3
  23. package/src/components/RouteView/RouteView.tsx +13 -13
  24. package/src/components/RouteView/components.tsx +12 -6
  25. package/src/components/RouteView/helpers.tsx +19 -17
  26. package/src/components/RouterErrorBoundary.tsx +9 -17
  27. package/src/createSignalFromSource.ts +9 -1
  28. package/src/createStoreFromSource.ts +1 -3
  29. package/src/hooks/useRouteNode.tsx +1 -2
  30. package/src/hooks/useRouteStore.tsx +0 -8
  31. package/src/hooks/useRouterTransition.tsx +3 -3
  32. package/dist/types/hooks/useRouterError.d.ts +0 -4
  33. package/dist/types/hooks/useRouterError.d.ts.map +0 -1
  34. package/src/hooks/useRouterError.tsx +0 -23
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
- const MATCH_MARKER = Symbol.for("RouteView.Match");
12
- const NOT_FOUND_MARKER = Symbol.for("RouteView.NotFound");
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,37 +77,44 @@ 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
- const isActive = !activeMatchFound && isSegmentMatch(routeName, fullSegmentName, exact);
79
- if (isActive) {
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(() => source.getSnapshot());
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
  });
@@ -119,27 +134,24 @@ const useRouter = () => {
119
134
 
120
135
  function useRouteNode(nodeName) {
121
136
  const router = useRouter();
122
- const store = sources.createRouteNodeSource(router, nodeName);
123
- return createSignalFromSource(store);
137
+ return createSignalFromSource(sources.createRouteNodeSource(router, nodeName));
124
138
  }
125
139
 
126
140
  function RouteViewRoot(props) {
127
141
  const routeState = useRouteNode(props.nodeName);
128
142
  const resolved = solidJs.children(() => props.children);
143
+ const elements = solidJs.createMemo(() => {
144
+ const arr = [];
145
+ collectElements(resolved(), arr);
146
+ return arr;
147
+ });
129
148
  return web.memo(() => {
130
149
  const state = routeState();
131
150
  if (!state.route) {
132
151
  return null;
133
152
  }
134
- const elements = [];
135
- collectElements(resolved(), elements);
136
- const {
137
- rendered
138
- } = buildRenderList(elements, state.route.name, props.nodeName);
139
- if (rendered.length > 0) {
140
- return rendered;
141
- }
142
- return null;
153
+ const rendered = buildRenderList(elements(), state.route.name, props.nodeName);
154
+ return rendered.length > 0 ? rendered : null;
143
155
  });
144
156
  }
145
157
  RouteViewRoot.displayName = "RouteView";
@@ -165,14 +177,36 @@ const INTERNAL_ROUTE_PREFIX = "@@";
165
177
  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
178
  function createRouteAnnouncer(router, options) {
167
179
  const prefix = "Navigated to ";
180
+ const getCustomText = options?.getAnnouncementText;
168
181
  let isInitialNavigation = true;
169
182
  let isReady = false;
170
183
  let isDestroyed = false;
171
184
  let lastAnnouncedText = "";
185
+ let pendingText = null;
172
186
  let clearTimeoutId;
173
187
  const announcer = getOrCreateAnnouncer();
188
+ const doAnnounce = (text, h1) => {
189
+ lastAnnouncedText = text;
190
+ clearTimeout(clearTimeoutId);
191
+ announcer.textContent = text;
192
+ clearTimeoutId = setTimeout(() => {
193
+ announcer.textContent = "";
194
+ lastAnnouncedText = "";
195
+ }, CLEAR_DELAY);
196
+ manageFocus(h1);
197
+ };
198
+
199
+ // Safari-ready delay: announcing before VoiceOver wires up the aria-live region
200
+ // causes the first announcement to be silently dropped. Wait SAFARI_READY_DELAY ms
201
+ // before marking the announcer "ready" — any navigation during that window is
202
+ // buffered in pendingText and flushed once the delay expires.
174
203
  const safariTimeoutId = setTimeout(() => {
175
204
  isReady = true;
205
+ if (pendingText !== null && !isDestroyed) {
206
+ const text = pendingText;
207
+ pendingText = null;
208
+ doAnnounce(text, document.querySelector("h1"));
209
+ }
176
210
  }, SAFARI_READY_DELAY);
177
211
  const unsubscribe = router.subscribe(({
178
212
  route
@@ -181,22 +215,28 @@ function createRouteAnnouncer(router, options) {
181
215
  isInitialNavigation = false;
182
216
  return;
183
217
  }
218
+
219
+ // Double rAF: waits for two paint frames so the incoming route's DOM
220
+ // (including the new <h1>) is fully rendered before resolveText reads it.
221
+ // Single rAF fires before the new route's template has been attached,
222
+ // which would cause resolveText to pick up the OLD h1 or fall back to
223
+ // document.title / route.name prematurely.
184
224
  requestAnimationFrame(() => {
185
225
  requestAnimationFrame(() => {
186
226
  if (isDestroyed) {
187
227
  return;
188
228
  }
189
- const text = resolveText(route, prefix);
190
- if (text && text !== lastAnnouncedText && isReady) {
191
- lastAnnouncedText = text;
192
- clearTimeout(clearTimeoutId);
193
- announcer.textContent = text;
194
- clearTimeoutId = setTimeout(() => {
195
- announcer.textContent = "";
196
- lastAnnouncedText = "";
197
- }, CLEAR_DELAY);
198
- manageFocus();
229
+ const h1 = document.querySelector("h1");
230
+ const text = resolveText(route, prefix, getCustomText, h1);
231
+ if (!text || text === lastAnnouncedText) {
232
+ return;
233
+ }
234
+ if (!isReady) {
235
+ // Defer announcement until Safari-ready window elapses (see safariTimeoutId).
236
+ pendingText = text;
237
+ return;
199
238
  }
239
+ doAnnounce(text, h1);
200
240
  });
201
241
  });
202
242
  });
@@ -226,15 +266,13 @@ function getOrCreateAnnouncer() {
226
266
  function removeAnnouncer() {
227
267
  document.querySelector(`[${ANNOUNCER_ATTR}]`)?.remove();
228
268
  }
229
- function resolveText(route, prefix, getCustomText) {
230
- const h1 = document.querySelector("h1");
269
+ function resolveText(route, prefix, getCustomText, h1) {
231
270
  const h1Text = h1?.textContent.trim() ?? "";
232
271
  const routeName = route.name.startsWith(INTERNAL_ROUTE_PREFIX) ? "" : route.name;
233
272
  const rawText = h1Text || document.title || routeName || globalThis.location.pathname;
234
273
  return `${prefix}${rawText}`;
235
274
  }
236
- function manageFocus() {
237
- const h1 = document.querySelector("h1");
275
+ function manageFocus(h1) {
238
276
  if (!h1) {
239
277
  return;
240
278
  }
@@ -253,7 +291,10 @@ function buildHref(router, routeName, routeParams) {
253
291
  try {
254
292
  const buildUrl = router.buildUrl;
255
293
  if (buildUrl) {
256
- return buildUrl(routeName, routeParams);
294
+ const url = buildUrl(routeName, routeParams);
295
+ if (url !== undefined) {
296
+ return url;
297
+ }
257
298
  }
258
299
  return router.buildPath(routeName, routeParams);
259
300
  } catch {
@@ -261,20 +302,41 @@ function buildHref(router, routeName, routeParams) {
261
302
  return undefined;
262
303
  }
263
304
  }
305
+ function parseTokens(value) {
306
+ return value ? value.match(/\S+/g) ?? [] : [];
307
+ }
264
308
  function buildActiveClassName(isActive, activeClassName, baseClassName) {
265
309
  if (isActive && activeClassName) {
266
- return baseClassName ? `${baseClassName} ${activeClassName}`.trim() : activeClassName;
310
+ const activeTokens = parseTokens(activeClassName);
311
+ if (activeTokens.length === 0) {
312
+ return baseClassName ?? undefined;
313
+ }
314
+ if (!baseClassName) {
315
+ return activeTokens.join(" ");
316
+ }
317
+ const baseTokens = parseTokens(baseClassName);
318
+ const seen = new Set(baseTokens);
319
+ for (const token of activeTokens) {
320
+ if (!seen.has(token)) {
321
+ seen.add(token);
322
+ baseTokens.push(token);
323
+ }
324
+ }
325
+ return baseTokens.join(" ");
267
326
  }
268
327
  return baseClassName ?? undefined;
269
328
  }
270
329
  function applyLinkA11y(element) {
330
+ if (!element) {
331
+ return;
332
+ }
271
333
  if (element instanceof HTMLAnchorElement || element instanceof HTMLButtonElement) {
272
334
  return;
273
335
  }
274
- if (!element.getAttribute("role")) {
336
+ if (!element.hasAttribute("role")) {
275
337
  element.setAttribute("role", "link");
276
338
  }
277
- if (!element.getAttribute("tabindex")) {
339
+ if (!element.hasAttribute("tabindex")) {
278
340
  element.setAttribute("tabindex", "0");
279
341
  }
280
342
  }
@@ -289,9 +351,12 @@ function Link(props) {
289
351
  ignoreQueryParams: true
290
352
  }, props);
291
353
  const [local, rest] = solidJs.splitProps(merged, ["routeName", "routeParams", "routeOptions", "activeClassName", "activeStrict", "ignoreQueryParams", "onClick", "target", "class", "children"]);
292
- const router = useRouter();
293
354
  const ctx = solidJs.useContext(RouterContext);
294
- const useFastPath = ctx?.routeSelector && !local.activeStrict && local.ignoreQueryParams && local.routeParams === EMPTY_PARAMS;
355
+ if (!ctx) {
356
+ throw new Error("Link must be used within a RouterProvider");
357
+ }
358
+ const router = ctx.router;
359
+ const useFastPath = !local.activeStrict && local.ignoreQueryParams && local.routeParams === EMPTY_PARAMS;
295
360
  const isActive = useFastPath ? () => ctx.routeSelector(local.routeName) : createSignalFromSource(sources.createActiveRouteSource(router, local.routeName, local.routeParams, {
296
361
  strict: local.activeStrict,
297
362
  ignoreQueryParams: local.ignoreQueryParams
@@ -327,34 +392,20 @@ function Link(props) {
327
392
  })();
328
393
  }
329
394
 
330
- const cache = new WeakMap();
331
- function useRouterError() {
332
- const router = useRouter();
333
- let source = cache.get(router);
334
- if (!source) {
335
- source = sources.createErrorSource(router);
336
- cache.set(router, source);
337
- }
338
- return createSignalFromSource(source);
339
- }
340
-
341
395
  function RouterErrorBoundary(props) {
342
- const snapshot = useRouterError();
343
- const [dismissedVersion, setDismissedVersion] = solidJs.createSignal(-1);
396
+ const router = useRouter();
397
+ const snapshot = createSignalFromSource(sources.createDismissableError(router));
344
398
  solidJs.createEffect(() => {
345
399
  const snap = snapshot();
346
400
  if (snap.error) {
347
401
  props.onError?.(snap.error, snap.toRoute, snap.fromRoute);
348
402
  }
349
403
  });
350
- const visibleError = solidJs.createMemo(() => {
351
- const snap = snapshot();
352
- return snap.version > dismissedVersion() ? snap.error : null;
353
- });
354
- const resetError = () => setDismissedVersion(snapshot().version);
355
- return [web.memo(() => props.children), web.memo(() => {
356
- const error = visibleError();
357
- return error ? props.fallback(error, resetError) : null;
404
+ return [web.memo(() => props.children), web.createComponent(solidJs.Show, {
405
+ get when() {
406
+ return snapshot().error;
407
+ },
408
+ children: error => props.fallback(error(), snapshot().resetError)
358
409
  })];
359
410
  }
360
411
 
@@ -424,7 +475,9 @@ const useRoute = () => {
424
475
  };
425
476
 
426
477
  function createStoreFromSource(source) {
427
- const [state, setState] = store.createStore(structuredClone(source.getSnapshot()));
478
+ const [state, setState] = store.createStore({
479
+ ...source.getSnapshot()
480
+ });
428
481
  const unsubscribe = source.subscribe(() => {
429
482
  setState(store.reconcile(source.getSnapshot()));
430
483
  });
@@ -433,10 +486,6 @@ function createStoreFromSource(source) {
433
486
  }
434
487
 
435
488
  function useRouteStore() {
436
- const ctx = solidJs.useContext(RouteContext);
437
- if (!ctx) {
438
- throw new Error("useRouteStore must be used within a RouterProvider");
439
- }
440
489
  const router = useRouter();
441
490
  return createStoreFromSource(sources.createRouteSource(router));
442
491
  }
@@ -448,8 +497,8 @@ function useRouteNodeStore(nodeName) {
448
497
 
449
498
  function useRouterTransition() {
450
499
  const router = useRouter();
451
- const store = sources.createTransitionSource(router);
452
- return createSignalFromSource(store);
500
+ const source = sources.getTransitionSource(router);
501
+ return createSignalFromSource(source);
453
502
  }
454
503
 
455
504
  function isRouteActive(linkRouteName, currentRouteName) {
@@ -1,13 +1,15 @@
1
1
  import { createComponent, memo, spread, mergeProps as mergeProps$1, insert, template } from 'solid-js/web';
2
- import { createRouteNodeSource, createActiveRouteSource, createErrorSource, createRouteSource, createTransitionSource } from '@real-router/sources';
3
- import { Suspense, createSignal, onCleanup, createContext, useContext, children, mergeProps, splitProps, createMemo, createEffect, onMount, createSelector } from 'solid-js';
2
+ import { createRouteNodeSource, createActiveRouteSource, createDismissableError, createRouteSource, getTransitionSource } from '@real-router/sources';
3
+ import { Suspense, createSignal, onCleanup, createContext, useContext, children, createMemo, mergeProps, splitProps, createEffect, Show, onMount, createSelector } from 'solid-js';
4
4
  import { getPluginApi } from '@real-router/core/api';
5
5
  import { startsWithSegment, getRouteUtils } from '@real-router/route-utils';
6
6
  import { createStore, reconcile } from 'solid-js/store';
7
7
  import { UNKNOWN_ROUTE, getNavigator } from '@real-router/core';
8
8
 
9
- const MATCH_MARKER = Symbol.for("RouteView.Match");
10
- const NOT_FOUND_MARKER = Symbol.for("RouteView.NotFound");
9
+ // Local (non-global) Symbols — Symbol.for() would expose markers to spoofing
10
+ // via the global Symbol registry. See Gotchas section "RouteView Marker Objects".
11
+ const MATCH_MARKER = Symbol("RouteView.Match");
12
+ const NOT_FOUND_MARKER = Symbol("RouteView.NotFound");
11
13
  function Match(props) {
12
14
  const result = {
13
15
  $$type: MATCH_MARKER,
@@ -18,6 +20,10 @@ function Match(props) {
18
20
  return props.children;
19
21
  }
20
22
  };
23
+
24
+ // Marker object is identified by $$type Symbol in RouteView/helpers.tsx,
25
+ // not rendered as JSX. Cast required because JSX.Element does not include
26
+ // arbitrary marker shapes.
21
27
  return result;
22
28
  }
23
29
  Match.displayName = "RouteView.Match";
@@ -28,6 +34,8 @@ function NotFound(props) {
28
34
  return props.children;
29
35
  }
30
36
  };
37
+
38
+ // See Match for the marker-pattern rationale.
31
39
  return result;
32
40
  }
33
41
  NotFound.displayName = "RouteView.NotFound";
@@ -67,37 +75,44 @@ function buildRenderList(elements, routeName, nodeName) {
67
75
  notFoundChildren = child.children;
68
76
  continue;
69
77
  }
78
+ if (activeMatchFound) {
79
+ continue;
80
+ }
70
81
  const {
71
82
  segment,
72
83
  exact,
73
84
  fallback
74
85
  } = child;
75
86
  const fullSegmentName = nodeName ? `${nodeName}.${segment}` : segment;
76
- const isActive = !activeMatchFound && isSegmentMatch(routeName, fullSegmentName, exact);
77
- if (isActive) {
78
- activeMatchFound = true;
79
- const matchContent = child.children;
80
- const content = fallback === undefined ? matchContent : createComponent(Suspense, {
81
- fallback: fallback,
82
- children: matchContent
83
- });
84
- rendered.push(content);
87
+ if (!isSegmentMatch(routeName, fullSegmentName, exact)) {
88
+ continue;
85
89
  }
90
+ activeMatchFound = true;
91
+ rendered.push(fallback === undefined ? child.children : createComponent(Suspense, {
92
+ fallback: fallback,
93
+ get children() {
94
+ return child.children;
95
+ }
96
+ }));
86
97
  }
87
98
  if (!activeMatchFound && routeName === UNKNOWN_ROUTE && notFoundChildren !== null) {
88
99
  rendered.push(notFoundChildren);
89
100
  }
90
- return {
91
- rendered,
92
- activeMatchFound
93
- };
101
+ return rendered;
94
102
  }
95
103
 
96
104
  function createSignalFromSource(source) {
97
105
  const [value, setValue] = createSignal(source.getSnapshot());
106
+ const sync = () => source.getSnapshot();
98
107
  const unsubscribe = source.subscribe(() => {
99
- setValue(() => source.getSnapshot());
108
+ setValue(sync);
100
109
  });
110
+
111
+ // Re-read after subscribe: lazy sources reconcile their snapshot in
112
+ // onFirstSubscribe (when reused after disconnect via cache). Listener is not
113
+ // notified for that internal update, so we must sync the signal manually.
114
+ // No-op when snapshot is unchanged (signal equality check).
115
+ setValue(sync);
101
116
  onCleanup(() => {
102
117
  unsubscribe();
103
118
  });
@@ -117,27 +132,24 @@ const useRouter = () => {
117
132
 
118
133
  function useRouteNode(nodeName) {
119
134
  const router = useRouter();
120
- const store = createRouteNodeSource(router, nodeName);
121
- return createSignalFromSource(store);
135
+ return createSignalFromSource(createRouteNodeSource(router, nodeName));
122
136
  }
123
137
 
124
138
  function RouteViewRoot(props) {
125
139
  const routeState = useRouteNode(props.nodeName);
126
140
  const resolved = children(() => props.children);
141
+ const elements = createMemo(() => {
142
+ const arr = [];
143
+ collectElements(resolved(), arr);
144
+ return arr;
145
+ });
127
146
  return memo(() => {
128
147
  const state = routeState();
129
148
  if (!state.route) {
130
149
  return null;
131
150
  }
132
- const elements = [];
133
- collectElements(resolved(), elements);
134
- const {
135
- rendered
136
- } = buildRenderList(elements, state.route.name, props.nodeName);
137
- if (rendered.length > 0) {
138
- return rendered;
139
- }
140
- return null;
151
+ const rendered = buildRenderList(elements(), state.route.name, props.nodeName);
152
+ return rendered.length > 0 ? rendered : null;
141
153
  });
142
154
  }
143
155
  RouteViewRoot.displayName = "RouteView";
@@ -163,14 +175,36 @@ const INTERNAL_ROUTE_PREFIX = "@@";
163
175
  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";
164
176
  function createRouteAnnouncer(router, options) {
165
177
  const prefix = "Navigated to ";
178
+ const getCustomText = options?.getAnnouncementText;
166
179
  let isInitialNavigation = true;
167
180
  let isReady = false;
168
181
  let isDestroyed = false;
169
182
  let lastAnnouncedText = "";
183
+ let pendingText = null;
170
184
  let clearTimeoutId;
171
185
  const announcer = getOrCreateAnnouncer();
186
+ const doAnnounce = (text, h1) => {
187
+ lastAnnouncedText = text;
188
+ clearTimeout(clearTimeoutId);
189
+ announcer.textContent = text;
190
+ clearTimeoutId = setTimeout(() => {
191
+ announcer.textContent = "";
192
+ lastAnnouncedText = "";
193
+ }, CLEAR_DELAY);
194
+ manageFocus(h1);
195
+ };
196
+
197
+ // Safari-ready delay: announcing before VoiceOver wires up the aria-live region
198
+ // causes the first announcement to be silently dropped. Wait SAFARI_READY_DELAY ms
199
+ // before marking the announcer "ready" — any navigation during that window is
200
+ // buffered in pendingText and flushed once the delay expires.
172
201
  const safariTimeoutId = setTimeout(() => {
173
202
  isReady = true;
203
+ if (pendingText !== null && !isDestroyed) {
204
+ const text = pendingText;
205
+ pendingText = null;
206
+ doAnnounce(text, document.querySelector("h1"));
207
+ }
174
208
  }, SAFARI_READY_DELAY);
175
209
  const unsubscribe = router.subscribe(({
176
210
  route
@@ -179,22 +213,28 @@ function createRouteAnnouncer(router, options) {
179
213
  isInitialNavigation = false;
180
214
  return;
181
215
  }
216
+
217
+ // Double rAF: waits for two paint frames so the incoming route's DOM
218
+ // (including the new <h1>) is fully rendered before resolveText reads it.
219
+ // Single rAF fires before the new route's template has been attached,
220
+ // which would cause resolveText to pick up the OLD h1 or fall back to
221
+ // document.title / route.name prematurely.
182
222
  requestAnimationFrame(() => {
183
223
  requestAnimationFrame(() => {
184
224
  if (isDestroyed) {
185
225
  return;
186
226
  }
187
- const text = resolveText(route, prefix);
188
- if (text && text !== lastAnnouncedText && isReady) {
189
- lastAnnouncedText = text;
190
- clearTimeout(clearTimeoutId);
191
- announcer.textContent = text;
192
- clearTimeoutId = setTimeout(() => {
193
- announcer.textContent = "";
194
- lastAnnouncedText = "";
195
- }, CLEAR_DELAY);
196
- manageFocus();
227
+ const h1 = document.querySelector("h1");
228
+ const text = resolveText(route, prefix, getCustomText, h1);
229
+ if (!text || text === lastAnnouncedText) {
230
+ return;
231
+ }
232
+ if (!isReady) {
233
+ // Defer announcement until Safari-ready window elapses (see safariTimeoutId).
234
+ pendingText = text;
235
+ return;
197
236
  }
237
+ doAnnounce(text, h1);
198
238
  });
199
239
  });
200
240
  });
@@ -224,15 +264,13 @@ function getOrCreateAnnouncer() {
224
264
  function removeAnnouncer() {
225
265
  document.querySelector(`[${ANNOUNCER_ATTR}]`)?.remove();
226
266
  }
227
- function resolveText(route, prefix, getCustomText) {
228
- const h1 = document.querySelector("h1");
267
+ function resolveText(route, prefix, getCustomText, h1) {
229
268
  const h1Text = h1?.textContent.trim() ?? "";
230
269
  const routeName = route.name.startsWith(INTERNAL_ROUTE_PREFIX) ? "" : route.name;
231
270
  const rawText = h1Text || document.title || routeName || globalThis.location.pathname;
232
271
  return `${prefix}${rawText}`;
233
272
  }
234
- function manageFocus() {
235
- const h1 = document.querySelector("h1");
273
+ function manageFocus(h1) {
236
274
  if (!h1) {
237
275
  return;
238
276
  }
@@ -251,7 +289,10 @@ function buildHref(router, routeName, routeParams) {
251
289
  try {
252
290
  const buildUrl = router.buildUrl;
253
291
  if (buildUrl) {
254
- return buildUrl(routeName, routeParams);
292
+ const url = buildUrl(routeName, routeParams);
293
+ if (url !== undefined) {
294
+ return url;
295
+ }
255
296
  }
256
297
  return router.buildPath(routeName, routeParams);
257
298
  } catch {
@@ -259,20 +300,41 @@ function buildHref(router, routeName, routeParams) {
259
300
  return undefined;
260
301
  }
261
302
  }
303
+ function parseTokens(value) {
304
+ return value ? value.match(/\S+/g) ?? [] : [];
305
+ }
262
306
  function buildActiveClassName(isActive, activeClassName, baseClassName) {
263
307
  if (isActive && activeClassName) {
264
- return baseClassName ? `${baseClassName} ${activeClassName}`.trim() : activeClassName;
308
+ const activeTokens = parseTokens(activeClassName);
309
+ if (activeTokens.length === 0) {
310
+ return baseClassName ?? undefined;
311
+ }
312
+ if (!baseClassName) {
313
+ return activeTokens.join(" ");
314
+ }
315
+ const baseTokens = parseTokens(baseClassName);
316
+ const seen = new Set(baseTokens);
317
+ for (const token of activeTokens) {
318
+ if (!seen.has(token)) {
319
+ seen.add(token);
320
+ baseTokens.push(token);
321
+ }
322
+ }
323
+ return baseTokens.join(" ");
265
324
  }
266
325
  return baseClassName ?? undefined;
267
326
  }
268
327
  function applyLinkA11y(element) {
328
+ if (!element) {
329
+ return;
330
+ }
269
331
  if (element instanceof HTMLAnchorElement || element instanceof HTMLButtonElement) {
270
332
  return;
271
333
  }
272
- if (!element.getAttribute("role")) {
334
+ if (!element.hasAttribute("role")) {
273
335
  element.setAttribute("role", "link");
274
336
  }
275
- if (!element.getAttribute("tabindex")) {
337
+ if (!element.hasAttribute("tabindex")) {
276
338
  element.setAttribute("tabindex", "0");
277
339
  }
278
340
  }
@@ -287,9 +349,12 @@ function Link(props) {
287
349
  ignoreQueryParams: true
288
350
  }, props);
289
351
  const [local, rest] = splitProps(merged, ["routeName", "routeParams", "routeOptions", "activeClassName", "activeStrict", "ignoreQueryParams", "onClick", "target", "class", "children"]);
290
- const router = useRouter();
291
352
  const ctx = useContext(RouterContext);
292
- const useFastPath = ctx?.routeSelector && !local.activeStrict && local.ignoreQueryParams && local.routeParams === EMPTY_PARAMS;
353
+ if (!ctx) {
354
+ throw new Error("Link must be used within a RouterProvider");
355
+ }
356
+ const router = ctx.router;
357
+ const useFastPath = !local.activeStrict && local.ignoreQueryParams && local.routeParams === EMPTY_PARAMS;
293
358
  const isActive = useFastPath ? () => ctx.routeSelector(local.routeName) : createSignalFromSource(createActiveRouteSource(router, local.routeName, local.routeParams, {
294
359
  strict: local.activeStrict,
295
360
  ignoreQueryParams: local.ignoreQueryParams
@@ -325,34 +390,20 @@ function Link(props) {
325
390
  })();
326
391
  }
327
392
 
328
- const cache = new WeakMap();
329
- function useRouterError() {
330
- const router = useRouter();
331
- let source = cache.get(router);
332
- if (!source) {
333
- source = createErrorSource(router);
334
- cache.set(router, source);
335
- }
336
- return createSignalFromSource(source);
337
- }
338
-
339
393
  function RouterErrorBoundary(props) {
340
- const snapshot = useRouterError();
341
- const [dismissedVersion, setDismissedVersion] = createSignal(-1);
394
+ const router = useRouter();
395
+ const snapshot = createSignalFromSource(createDismissableError(router));
342
396
  createEffect(() => {
343
397
  const snap = snapshot();
344
398
  if (snap.error) {
345
399
  props.onError?.(snap.error, snap.toRoute, snap.fromRoute);
346
400
  }
347
401
  });
348
- const visibleError = createMemo(() => {
349
- const snap = snapshot();
350
- return snap.version > dismissedVersion() ? snap.error : null;
351
- });
352
- const resetError = () => setDismissedVersion(snapshot().version);
353
- return [memo(() => props.children), memo(() => {
354
- const error = visibleError();
355
- return error ? props.fallback(error, resetError) : null;
402
+ return [memo(() => props.children), createComponent(Show, {
403
+ get when() {
404
+ return snapshot().error;
405
+ },
406
+ children: error => props.fallback(error(), snapshot().resetError)
356
407
  })];
357
408
  }
358
409
 
@@ -422,7 +473,9 @@ const useRoute = () => {
422
473
  };
423
474
 
424
475
  function createStoreFromSource(source) {
425
- const [state, setState] = createStore(structuredClone(source.getSnapshot()));
476
+ const [state, setState] = createStore({
477
+ ...source.getSnapshot()
478
+ });
426
479
  const unsubscribe = source.subscribe(() => {
427
480
  setState(reconcile(source.getSnapshot()));
428
481
  });
@@ -431,10 +484,6 @@ function createStoreFromSource(source) {
431
484
  }
432
485
 
433
486
  function useRouteStore() {
434
- const ctx = useContext(RouteContext);
435
- if (!ctx) {
436
- throw new Error("useRouteStore must be used within a RouterProvider");
437
- }
438
487
  const router = useRouter();
439
488
  return createStoreFromSource(createRouteSource(router));
440
489
  }
@@ -446,8 +495,8 @@ function useRouteNodeStore(nodeName) {
446
495
 
447
496
  function useRouterTransition() {
448
497
  const router = useRouter();
449
- const store = createTransitionSource(router);
450
- return createSignalFromSource(store);
498
+ const source = getTransitionSource(router);
499
+ return createSignalFromSource(source);
451
500
  }
452
501
 
453
502
  function isRouteActive(linkRouteName, currentRouteName) {
@@ -4,5 +4,6 @@ export interface RouteProviderProps {
4
4
  router: Router;
5
5
  announceNavigation?: boolean;
6
6
  }
7
+ export declare function isRouteActive(linkRouteName: string, currentRouteName: string): boolean;
7
8
  export declare function RouterProvider(props: ParentProps<RouteProviderProps>): JSX.Element;
8
9
  //# sourceMappingURL=RouterProvider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"RouterProvider.d.ts","sourceRoot":"","sources":["../../src/RouterProvider.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEjD,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAYD,wBAAgB,cAAc,CAC5B,KAAK,EAAE,WAAW,CAAC,kBAAkB,CAAC,GACrC,GAAG,CAAC,OAAO,CA+Bb"}
1
+ {"version":3,"file":"RouterProvider.d.ts","sourceRoot":"","sources":["../../src/RouterProvider.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEjD,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,wBAAgB,aAAa,CAC3B,aAAa,EAAE,MAAM,EACrB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAKT;AAED,wBAAgB,cAAc,CAC5B,KAAK,EAAE,WAAW,CAAC,kBAAkB,CAAC,GACrC,GAAG,CAAC,OAAO,CA+Bb"}
@@ -1 +1 @@
1
- {"version":3,"file":"Link.d.ts","sourceRoot":"","sources":["../../../src/components/Link.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEpC,wBAAgB,IAAI,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAC5C,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAC5B,GAAG,CAAC,OAAO,CAgFb"}
1
+ {"version":3,"file":"Link.d.ts","sourceRoot":"","sources":["../../../src/components/Link.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEpC,wBAAgB,IAAI,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAC5C,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAC5B,GAAG,CAAC,OAAO,CAoFb"}
@@ -1 +1 @@
1
- {"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../../../../src/components/RouteView/components.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACzD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEpC,eAAO,MAAM,YAAY,eAAgC,CAAC;AAE1D,eAAO,MAAM,gBAAgB,eAAmC,CAAC;AAEjE,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,YAAY,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;IACvB,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,OAAO,gBAAgB,CAAC;IAChC,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC;CACvB;AAED,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,cAAc,CAAC;AAE3D,wBAAgB,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG,CAAC,OAAO,CAYpD;yBAZe,KAAK;;;AAgBrB,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,GAAG,CAAC,OAAO,CAS1D;yBATe,QAAQ"}
1
+ {"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../../../../src/components/RouteView/components.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACzD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAIpC,eAAO,MAAM,YAAY,eAA4B,CAAC;AAEtD,eAAO,MAAM,gBAAgB,eAA+B,CAAC;AAE7D,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,YAAY,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;IACvB,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,OAAO,gBAAgB,CAAC;IAChC,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC;CACvB;AAED,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,cAAc,CAAC;AAE3D,wBAAgB,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG,CAAC,OAAO,CAepD;yBAfe,KAAK;;;AAmBrB,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,GAAG,CAAC,OAAO,CAU1D;yBAVe,QAAQ"}
@@ -1,8 +1,6 @@
1
1
  import type { RouteViewMarker } from "./components";
2
2
  import type { JSX } from "solid-js";
3
+ export declare function isSegmentMatch(routeName: string, fullSegmentName: string, exact: boolean): boolean;
3
4
  export declare function collectElements(children: unknown, result: RouteViewMarker[]): void;
4
- export declare function buildRenderList(elements: RouteViewMarker[], routeName: string, nodeName: string): {
5
- rendered: JSX.Element[];
6
- activeMatchFound: boolean;
7
- };
5
+ export declare function buildRenderList(elements: RouteViewMarker[], routeName: string, nodeName: string): JSX.Element[];
8
6
  //# sourceMappingURL=helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../../src/components/RouteView/helpers.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAGV,eAAe,EAChB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAgCpC,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,OAAO,EACjB,MAAM,EAAE,eAAe,EAAE,GACxB,IAAI,CAgBN;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,eAAe,EAAE,EAC3B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf;IAAE,QAAQ,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC;IAAC,gBAAgB,EAAE,OAAO,CAAA;CAAE,CAuCxD"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../../src/components/RouteView/helpers.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAGV,eAAe,EAChB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEpC,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,MAAM,EACvB,KAAK,EAAE,OAAO,GACb,OAAO,CAMT;AAoBD,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,OAAO,EACjB,MAAM,EAAE,eAAe,EAAE,GACxB,IAAI,CAgBN;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,eAAe,EAAE,EAC3B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,GAAG,CAAC,OAAO,EAAE,CAyCf"}
@@ -1 +1 @@
1
- {"version":3,"file":"RouterErrorBoundary.d.ts","sourceRoot":"","sources":["../../../src/components/RouterErrorBoundary.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEpC,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,CACjB,KAAK,EAAE,WAAW,EAClB,UAAU,EAAE,MAAM,IAAI,KACnB,GAAG,CAAC,OAAO,CAAC;IACjB,QAAQ,CAAC,OAAO,CAAC,EAAE,CACjB,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,KAAK,GAAG,IAAI,EACrB,SAAS,EAAE,KAAK,GAAG,IAAI,KACpB,IAAI,CAAC;CACX;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,wBAAwB,GAC9B,GAAG,CAAC,OAAO,CA8Bb"}
1
+ {"version":3,"file":"RouterErrorBoundary.d.ts","sourceRoot":"","sources":["../../../src/components/RouterErrorBoundary.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEpC,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,CACjB,KAAK,EAAE,WAAW,EAClB,UAAU,EAAE,MAAM,IAAI,KACnB,GAAG,CAAC,OAAO,CAAC;IACjB,QAAQ,CAAC,OAAO,CAAC,EAAE,CACjB,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,KAAK,GAAG,IAAI,EACrB,SAAS,EAAE,KAAK,GAAG,IAAI,KACpB,IAAI,CAAC;CACX;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,wBAAwB,GAC9B,GAAG,CAAC,OAAO,CAoBb"}
@@ -1 +1 @@
1
- {"version":3,"file":"createSignalFromSource.d.ts","sourceRoot":"","sources":["../../src/createSignalFromSource.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC,wBAAgB,sBAAsB,CAAC,CAAC,EACtC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GACtB,QAAQ,CAAC,CAAC,CAAC,CAYb"}
1
+ {"version":3,"file":"createSignalFromSource.d.ts","sourceRoot":"","sources":["../../src/createSignalFromSource.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC,wBAAgB,sBAAsB,CAAC,CAAC,EACtC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GACtB,QAAQ,CAAC,CAAC,CAAC,CAoBb"}
@@ -1 +1 @@
1
- {"version":3,"file":"createStoreFromSource.d.ts","sourceRoot":"","sources":["../../src/createStoreFromSource.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,MAAM,EACpD,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GACtB,CAAC,CAYH"}
1
+ {"version":3,"file":"createStoreFromSource.d.ts","sourceRoot":"","sources":["../../src/createStoreFromSource.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,MAAM,EACpD,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GACtB,CAAC,CAUH"}
@@ -1,4 +1,4 @@
1
1
  export { createRouteAnnouncer } from "./route-announcer.js";
2
- export { shouldNavigate, buildHref, buildActiveClassName, applyLinkA11y, } from "./link-utils.js";
2
+ export { shouldNavigate, buildHref, buildActiveClassName, shallowEqual, applyLinkA11y, } from "./link-utils.js";
3
3
  export type { RouteAnnouncerOptions } from "./route-announcer.js";
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/dom-utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAE5D,OAAO,EACL,cAAc,EACd,SAAS,EACT,oBAAoB,EACpB,aAAa,GACd,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/dom-utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAE5D,OAAO,EACL,cAAc,EACd,SAAS,EACT,oBAAoB,EACpB,YAAY,EACZ,aAAa,GACd,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC"}
@@ -2,5 +2,6 @@ import type { Router, Params } from "@real-router/core";
2
2
  export declare function shouldNavigate(evt: MouseEvent): boolean;
3
3
  export declare function buildHref(router: Router, routeName: string, routeParams: Params): string | undefined;
4
4
  export declare function buildActiveClassName(isActive: boolean, activeClassName: string | undefined, baseClassName: string | undefined): string | undefined;
5
- export declare function applyLinkA11y(element: HTMLElement): void;
5
+ export declare function shallowEqual(prev: object | undefined, next: object | undefined): boolean;
6
+ export declare function applyLinkA11y(element: HTMLElement | null | undefined): void;
6
7
  //# sourceMappingURL=link-utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"link-utils.d.ts","sourceRoot":"","sources":["../../../src/dom-utils/link-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAExD,wBAAgB,cAAc,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAQvD;AAID,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAClB,MAAM,GAAG,SAAS,CAgBpB;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,OAAO,EACjB,eAAe,EAAE,MAAM,GAAG,SAAS,EACnC,aAAa,EAAE,MAAM,GAAG,SAAS,GAChC,MAAM,GAAG,SAAS,CAQpB;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAaxD"}
1
+ {"version":3,"file":"link-utils.d.ts","sourceRoot":"","sources":["../../../src/dom-utils/link-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAExD,wBAAgB,cAAc,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAQvD;AAID,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAClB,MAAM,GAAG,SAAS,CAoBpB;AAMD,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,OAAO,EACjB,eAAe,EAAE,MAAM,GAAG,SAAS,EACnC,aAAa,EAAE,MAAM,GAAG,SAAS,GAChC,MAAM,GAAG,SAAS,CAyBpB;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,IAAI,EAAE,MAAM,GAAG,SAAS,GACvB,OAAO,CAwBT;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAgB3E"}
@@ -1 +1 @@
1
- {"version":3,"file":"route-announcer.d.ts","sourceRoot":"","sources":["../../../src/dom-utils/route-announcer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AASvD,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC;CAChD;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,qBAAqB,GAC9B;IAAE,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,CAuDzB"}
1
+ {"version":3,"file":"route-announcer.d.ts","sourceRoot":"","sources":["../../../src/dom-utils/route-announcer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AASvD,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC;CAChD;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,qBAAqB,GAC9B;IAAE,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,CAsFzB"}
@@ -1 +1 @@
1
- {"version":3,"file":"useRouteNode.d.ts","sourceRoot":"","sources":["../../../src/hooks/useRouteNode.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAAC,UAAU,CAAC,CAKnE"}
1
+ {"version":3,"file":"useRouteNode.d.ts","sourceRoot":"","sources":["../../../src/hooks/useRouteNode.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAAC,UAAU,CAAC,CAInE"}
@@ -1 +1 @@
1
- {"version":3,"file":"useRouteStore.d.ts","sourceRoot":"","sources":["../../../src/hooks/useRouteStore.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,wBAAgB,aAAa,IAAI,UAAU,CAU1C"}
1
+ {"version":3,"file":"useRouteStore.d.ts","sourceRoot":"","sources":["../../../src/hooks/useRouteStore.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,wBAAgB,aAAa,IAAI,UAAU,CAI1C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@real-router/solid",
3
- "version": "0.3.1",
3
+ "version": "0.4.1",
4
4
  "type": "commonjs",
5
5
  "description": "Solid.js integration for Real-Router",
6
6
  "main": "./dist/cjs/index.js",
@@ -53,7 +53,7 @@
53
53
  "dependencies": {
54
54
  "@real-router/core": "^0.48.0",
55
55
  "@real-router/route-utils": "^0.2.1",
56
- "@real-router/sources": "^0.5.1"
56
+ "@real-router/sources": "^0.6.0"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@babel/core": "7.29.0",
@@ -71,19 +71,19 @@
71
71
  "solid-js": "1.9.12",
72
72
  "vite-plugin-solid": "2.11.11",
73
73
  "vitest": "4.1.0",
74
- "@real-router/browser-plugin": "^0.12.1"
74
+ "@real-router/browser-plugin": "^0.13.0"
75
75
  },
76
76
  "peerDependencies": {
77
77
  "solid-js": ">=1.7.0"
78
78
  },
79
79
  "scripts": {
80
- "build": "rimraf dist && tsc -p tsconfig.build.json && rollup -c rollup.config.mjs",
81
80
  "test": "vitest",
82
81
  "test:properties": "vitest run --config vitest.config.properties.mts",
83
82
  "test:stress": "vitest run --config vitest.config.stress.mts",
84
83
  "type-check": "tsc --noEmit",
85
84
  "lint": "eslint --cache --ext .ts,.tsx src/ tests/ --fix --max-warnings 0",
86
85
  "lint:package": "publint",
87
- "lint:types": "attw --pack ."
86
+ "lint:types": "attw --pack .",
87
+ "bundle": "rimraf dist && tsc -p tsconfig.build.json && rollup -c rollup.config.mjs"
88
88
  }
89
89
  }
@@ -14,7 +14,7 @@ export interface RouteProviderProps {
14
14
  announceNavigation?: boolean;
15
15
  }
16
16
 
17
- function isRouteActive(
17
+ export function isRouteActive(
18
18
  linkRouteName: string,
19
19
  currentRouteName: string,
20
20
  ): boolean {
@@ -9,7 +9,6 @@ import {
9
9
  buildHref,
10
10
  buildActiveClassName,
11
11
  } from "../dom-utils/index.js";
12
- import { useRouter } from "../hooks/useRouter";
13
12
 
14
13
  import type { LinkProps } from "../types";
15
14
  import type { Params } from "@real-router/core";
@@ -42,11 +41,15 @@ export function Link<P extends Params = Params>(
42
41
  "children",
43
42
  ]);
44
43
 
45
- const router = useRouter();
46
44
  const ctx = useContext(RouterContext);
47
45
 
46
+ if (!ctx) {
47
+ throw new Error("Link must be used within a RouterProvider");
48
+ }
49
+
50
+ const router = ctx.router;
51
+
48
52
  const useFastPath =
49
- ctx?.routeSelector &&
50
53
  !local.activeStrict &&
51
54
  local.ignoreQueryParams &&
52
55
  local.routeParams === EMPTY_PARAMS;
@@ -1,4 +1,4 @@
1
- import { children as resolveChildren } from "solid-js";
1
+ import { children as resolveChildren, createMemo } from "solid-js";
2
2
 
3
3
  import { Match, NotFound } from "./components";
4
4
  import { buildRenderList, collectElements } from "./helpers";
@@ -13,6 +13,14 @@ function RouteViewRoot(props: Readonly<RouteViewProps>): JSX.Element {
13
13
 
14
14
  const resolved = resolveChildren(() => props.children);
15
15
 
16
+ const elements = createMemo(() => {
17
+ const arr: RouteViewMarker[] = [];
18
+
19
+ collectElements(resolved(), arr);
20
+
21
+ return arr;
22
+ });
23
+
16
24
  return (
17
25
  <>
18
26
  {(() => {
@@ -22,24 +30,16 @@ function RouteViewRoot(props: Readonly<RouteViewProps>): JSX.Element {
22
30
  return null;
23
31
  }
24
32
 
25
- const elements: RouteViewMarker[] = [];
26
-
27
- collectElements(resolved(), elements);
28
-
29
- const { rendered } = buildRenderList(
30
- elements,
33
+ const rendered = buildRenderList(
34
+ elements(),
31
35
  state.route.name,
32
36
  props.nodeName,
33
37
  );
34
38
 
35
- if (rendered.length > 0) {
36
- return rendered;
37
- }
38
-
39
- return null;
39
+ return rendered.length > 0 ? rendered : null;
40
40
  })()}
41
41
  </>
42
- ) as unknown as JSX.Element;
42
+ );
43
43
  }
44
44
 
45
45
  RouteViewRoot.displayName = "RouteView";
@@ -1,9 +1,11 @@
1
1
  import type { MatchProps, NotFoundProps } from "./types";
2
2
  import type { JSX } from "solid-js";
3
3
 
4
- export const MATCH_MARKER = Symbol.for("RouteView.Match");
4
+ // Local (non-global) Symbols Symbol.for() would expose markers to spoofing
5
+ // via the global Symbol registry. See Gotchas section "RouteView Marker Objects".
6
+ export const MATCH_MARKER = Symbol("RouteView.Match");
5
7
 
6
- export const NOT_FOUND_MARKER = Symbol.for("RouteView.NotFound");
8
+ export const NOT_FOUND_MARKER = Symbol("RouteView.NotFound");
7
9
 
8
10
  export interface MatchMarker {
9
11
  $$type: typeof MATCH_MARKER;
@@ -21,7 +23,7 @@ export interface NotFoundMarker {
21
23
  export type RouteViewMarker = MatchMarker | NotFoundMarker;
22
24
 
23
25
  export function Match(props: MatchProps): JSX.Element {
24
- const result = {
26
+ const result: MatchMarker = {
25
27
  $$type: MATCH_MARKER,
26
28
  segment: props.segment,
27
29
  exact: props.exact ?? false,
@@ -29,21 +31,25 @@ export function Match(props: MatchProps): JSX.Element {
29
31
  get children(): JSX.Element {
30
32
  return props.children;
31
33
  },
32
- } as MatchMarker;
34
+ };
33
35
 
36
+ // Marker object is identified by $$type Symbol in RouteView/helpers.tsx,
37
+ // not rendered as JSX. Cast required because JSX.Element does not include
38
+ // arbitrary marker shapes.
34
39
  return result as unknown as JSX.Element;
35
40
  }
36
41
 
37
42
  Match.displayName = "RouteView.Match";
38
43
 
39
44
  export function NotFound(props: NotFoundProps): JSX.Element {
40
- const result = {
45
+ const result: NotFoundMarker = {
41
46
  $$type: NOT_FOUND_MARKER,
42
47
  get children(): JSX.Element {
43
48
  return props.children;
44
49
  },
45
- } as NotFoundMarker;
50
+ };
46
51
 
52
+ // See Match for the marker-pattern rationale.
47
53
  return result as unknown as JSX.Element;
48
54
  }
49
55
 
@@ -11,7 +11,7 @@ import type {
11
11
  } from "./components";
12
12
  import type { JSX } from "solid-js";
13
13
 
14
- function isSegmentMatch(
14
+ export function isSegmentMatch(
15
15
  routeName: string,
16
16
  fullSegmentName: string,
17
17
  exact: boolean,
@@ -66,7 +66,7 @@ export function buildRenderList(
66
66
  elements: RouteViewMarker[],
67
67
  routeName: string,
68
68
  nodeName: string,
69
- ): { rendered: JSX.Element[]; activeMatchFound: boolean } {
69
+ ): JSX.Element[] {
70
70
  let notFoundChildren: JSX.Element | null = null;
71
71
  let activeMatchFound = false;
72
72
  const rendered: JSX.Element[] = [];
@@ -77,23 +77,25 @@ export function buildRenderList(
77
77
  continue;
78
78
  }
79
79
 
80
+ if (activeMatchFound) {
81
+ continue;
82
+ }
83
+
80
84
  const { segment, exact, fallback } = child;
81
85
  const fullSegmentName = nodeName ? `${nodeName}.${segment}` : segment;
82
- const isActive =
83
- !activeMatchFound && isSegmentMatch(routeName, fullSegmentName, exact);
84
-
85
- if (isActive) {
86
- activeMatchFound = true;
87
- const matchContent = child.children;
88
- const content =
89
- fallback === undefined ? (
90
- matchContent
91
- ) : (
92
- <Suspense fallback={fallback}>{matchContent}</Suspense>
93
- );
94
-
95
- rendered.push(content);
86
+
87
+ if (!isSegmentMatch(routeName, fullSegmentName, exact)) {
88
+ continue;
96
89
  }
90
+
91
+ activeMatchFound = true;
92
+ rendered.push(
93
+ fallback === undefined ? (
94
+ child.children
95
+ ) : (
96
+ <Suspense fallback={fallback}>{child.children}</Suspense>
97
+ ),
98
+ );
97
99
  }
98
100
 
99
101
  if (
@@ -104,5 +106,5 @@ export function buildRenderList(
104
106
  rendered.push(notFoundChildren);
105
107
  }
106
108
 
107
- return { rendered, activeMatchFound };
109
+ return rendered;
108
110
  }
@@ -1,6 +1,8 @@
1
- import { createEffect, createMemo, createSignal } from "solid-js";
1
+ import { createDismissableError } from "@real-router/sources";
2
+ import { createEffect, Show } from "solid-js";
2
3
 
3
- import { useRouterError } from "../hooks/useRouterError";
4
+ import { createSignalFromSource } from "../createSignalFromSource";
5
+ import { useRouter } from "../hooks/useRouter";
4
6
 
5
7
  import type { RouterError, State } from "@real-router/core";
6
8
  import type { JSX } from "solid-js";
@@ -21,8 +23,8 @@ export interface RouterErrorBoundaryProps {
21
23
  export function RouterErrorBoundary(
22
24
  props: RouterErrorBoundaryProps,
23
25
  ): JSX.Element {
24
- const snapshot = useRouterError();
25
- const [dismissedVersion, setDismissedVersion] = createSignal(-1);
26
+ const router = useRouter();
27
+ const snapshot = createSignalFromSource(createDismissableError(router));
26
28
 
27
29
  createEffect(() => {
28
30
  const snap = snapshot();
@@ -32,22 +34,12 @@ export function RouterErrorBoundary(
32
34
  }
33
35
  });
34
36
 
35
- const visibleError = createMemo(() => {
36
- const snap = snapshot();
37
-
38
- return snap.version > dismissedVersion() ? snap.error : null;
39
- });
40
-
41
- const resetError = () => setDismissedVersion(snapshot().version);
42
-
43
37
  return (
44
38
  <>
45
39
  {props.children}
46
- {(() => {
47
- const error = visibleError();
48
-
49
- return error ? props.fallback(error, resetError) : null;
50
- })()}
40
+ <Show when={snapshot().error}>
41
+ {(error) => props.fallback(error(), snapshot().resetError)}
42
+ </Show>
51
43
  </>
52
44
  );
53
45
  }
@@ -8,10 +8,18 @@ export function createSignalFromSource<T>(
8
8
  ): Accessor<T> {
9
9
  const [value, setValue] = createSignal<T>(source.getSnapshot());
10
10
 
11
+ const sync = (): T => source.getSnapshot();
12
+
11
13
  const unsubscribe = source.subscribe(() => {
12
- setValue(() => source.getSnapshot());
14
+ setValue(sync);
13
15
  });
14
16
 
17
+ // Re-read after subscribe: lazy sources reconcile their snapshot in
18
+ // onFirstSubscribe (when reused after disconnect via cache). Listener is not
19
+ // notified for that internal update, so we must sync the signal manually.
20
+ // No-op when snapshot is unchanged (signal equality check).
21
+ setValue(sync);
22
+
15
23
  onCleanup(() => {
16
24
  unsubscribe();
17
25
  });
@@ -6,9 +6,7 @@ import type { RouterSource } from "@real-router/sources";
6
6
  export function createStoreFromSource<T extends object>(
7
7
  source: RouterSource<T>,
8
8
  ): T {
9
- const [state, setState] = createStore<T>(
10
- structuredClone(source.getSnapshot()),
11
- );
9
+ const [state, setState] = createStore<T>({ ...source.getSnapshot() });
12
10
 
13
11
  const unsubscribe = source.subscribe(() => {
14
12
  setState(reconcile(source.getSnapshot()));
@@ -8,7 +8,6 @@ import type { Accessor } from "solid-js";
8
8
 
9
9
  export function useRouteNode(nodeName: string): Accessor<RouteState> {
10
10
  const router = useRouter();
11
- const store = createRouteNodeSource(router, nodeName);
12
11
 
13
- return createSignalFromSource(store);
12
+ return createSignalFromSource(createRouteNodeSource(router, nodeName));
14
13
  }
@@ -1,19 +1,11 @@
1
1
  import { createRouteSource } from "@real-router/sources";
2
- import { useContext } from "solid-js";
3
2
 
4
- import { RouteContext } from "../context";
5
3
  import { createStoreFromSource } from "../createStoreFromSource";
6
4
  import { useRouter } from "./useRouter";
7
5
 
8
6
  import type { RouteState } from "../types";
9
7
 
10
8
  export function useRouteStore(): RouteState {
11
- const ctx = useContext(RouteContext);
12
-
13
- if (!ctx) {
14
- throw new Error("useRouteStore must be used within a RouterProvider");
15
- }
16
-
17
9
  const router = useRouter();
18
10
 
19
11
  return createStoreFromSource(createRouteSource(router));
@@ -1,4 +1,4 @@
1
- import { createTransitionSource } from "@real-router/sources";
1
+ import { getTransitionSource } from "@real-router/sources";
2
2
 
3
3
  import { createSignalFromSource } from "../createSignalFromSource";
4
4
  import { useRouter } from "./useRouter";
@@ -8,7 +8,7 @@ import type { Accessor } from "solid-js";
8
8
 
9
9
  export function useRouterTransition(): Accessor<RouterTransitionSnapshot> {
10
10
  const router = useRouter();
11
- const store = createTransitionSource(router);
11
+ const source = getTransitionSource(router);
12
12
 
13
- return createSignalFromSource(store);
13
+ return createSignalFromSource(source);
14
14
  }
@@ -1,4 +0,0 @@
1
- import type { RouterErrorSnapshot } from "@real-router/sources";
2
- import type { Accessor } from "solid-js";
3
- export declare function useRouterError(): Accessor<RouterErrorSnapshot>;
4
- //# sourceMappingURL=useRouterError.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useRouterError.d.ts","sourceRoot":"","sources":["../../../src/hooks/useRouterError.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,mBAAmB,EAAgB,MAAM,sBAAsB,CAAC;AAC9E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAIzC,wBAAgB,cAAc,IAAI,QAAQ,CAAC,mBAAmB,CAAC,CAW9D"}
@@ -1,23 +0,0 @@
1
- import { createErrorSource } from "@real-router/sources";
2
-
3
- import { createSignalFromSource } from "../createSignalFromSource";
4
- import { useRouter } from "./useRouter";
5
-
6
- import type { Router } from "@real-router/core";
7
- import type { RouterErrorSnapshot, RouterSource } from "@real-router/sources";
8
- import type { Accessor } from "solid-js";
9
-
10
- const cache = new WeakMap<Router, RouterSource<RouterErrorSnapshot>>();
11
-
12
- export function useRouterError(): Accessor<RouterErrorSnapshot> {
13
- const router = useRouter();
14
-
15
- let source = cache.get(router);
16
-
17
- if (!source) {
18
- source = createErrorSource(router);
19
- cache.set(router, source);
20
- }
21
-
22
- return createSignalFromSource(source);
23
- }