@real-router/solid 0.6.0 → 0.8.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.
Files changed (38) hide show
  1. package/README.md +49 -4
  2. package/dist/cjs/index.d.ts +178 -2
  3. package/dist/cjs/index.js +407 -22
  4. package/dist/esm/index.d.mts +178 -2
  5. package/dist/esm/index.mjs +406 -23
  6. package/dist/types/RouterProvider.d.ts +1 -0
  7. package/dist/types/RouterProvider.d.ts.map +1 -1
  8. package/dist/types/components/RouteView/RouteView.d.ts +3 -2
  9. package/dist/types/components/RouteView/RouteView.d.ts.map +1 -1
  10. package/dist/types/components/RouteView/components.d.ts +12 -2
  11. package/dist/types/components/RouteView/components.d.ts.map +1 -1
  12. package/dist/types/components/RouteView/helpers.d.ts.map +1 -1
  13. package/dist/types/components/RouteView/index.d.ts +1 -1
  14. package/dist/types/components/RouteView/index.d.ts.map +1 -1
  15. package/dist/types/components/RouteView/types.d.ts +6 -0
  16. package/dist/types/components/RouteView/types.d.ts.map +1 -1
  17. package/dist/types/dom-utils/direction-tracker.d.ts +27 -0
  18. package/dist/types/dom-utils/direction-tracker.d.ts.map +1 -0
  19. package/dist/types/dom-utils/index.d.ts +4 -0
  20. package/dist/types/dom-utils/index.d.ts.map +1 -1
  21. package/dist/types/dom-utils/view-transitions.d.ts +6 -0
  22. package/dist/types/dom-utils/view-transitions.d.ts.map +1 -0
  23. package/dist/types/hooks/useRouteEnter.d.ts +76 -0
  24. package/dist/types/hooks/useRouteEnter.d.ts.map +1 -0
  25. package/dist/types/hooks/useRouteExit.d.ts +90 -0
  26. package/dist/types/hooks/useRouteExit.d.ts.map +1 -0
  27. package/dist/types/index.d.ts +5 -1
  28. package/dist/types/index.d.ts.map +1 -1
  29. package/package.json +3 -3
  30. package/src/RouterProvider.tsx +18 -1
  31. package/src/components/RouteView/RouteView.tsx +7 -2
  32. package/src/components/RouteView/components.tsx +25 -2
  33. package/src/components/RouteView/helpers.tsx +67 -21
  34. package/src/components/RouteView/index.ts +1 -0
  35. package/src/components/RouteView/types.ts +7 -0
  36. package/src/hooks/useRouteEnter.tsx +129 -0
  37. package/src/hooks/useRouteExit.tsx +123 -0
  38. package/src/index.tsx +17 -0
package/dist/cjs/index.js CHANGED
@@ -11,6 +11,7 @@ var core = require('@real-router/core');
11
11
  // Local (non-global) Symbols — Symbol.for() would expose markers to spoofing
12
12
  // via the global Symbol registry. See Gotchas section "RouteView Marker Objects".
13
13
  const MATCH_MARKER = Symbol("RouteView.Match");
14
+ const SELF_MARKER = Symbol("RouteView.Self");
14
15
  const NOT_FOUND_MARKER = Symbol("RouteView.NotFound");
15
16
  function Match(props) {
16
17
  const result = {
@@ -29,6 +30,19 @@ function Match(props) {
29
30
  return result;
30
31
  }
31
32
  Match.displayName = "RouteView.Match";
33
+ function Self(props) {
34
+ const result = {
35
+ $$type: SELF_MARKER,
36
+ fallback: props.fallback,
37
+ get children() {
38
+ return props.children;
39
+ }
40
+ };
41
+
42
+ // See Match for the marker-pattern rationale.
43
+ return result;
44
+ }
45
+ Self.displayName = "RouteView.Self";
32
46
  function NotFound(props) {
33
47
  const result = {
34
48
  $$type: NOT_FOUND_MARKER,
@@ -51,6 +65,9 @@ function isSegmentMatch(routeName, fullSegmentName, exact) {
51
65
  function isMatchMarker(value) {
52
66
  return value != null && typeof value === "object" && "$$type" in value && value.$$type === MATCH_MARKER;
53
67
  }
68
+ function isSelfMarker(value) {
69
+ return value != null && typeof value === "object" && "$$type" in value && value.$$type === SELF_MARKER;
70
+ }
54
71
  function isNotFoundMarker(value) {
55
72
  return value != null && typeof value === "object" && "$$type" in value && value.$$type === NOT_FOUND_MARKER;
56
73
  }
@@ -64,11 +81,48 @@ function collectElements(children, result) {
64
81
  }
65
82
  return;
66
83
  }
67
- if (isMatchMarker(children) || isNotFoundMarker(children)) {
84
+ if (isMatchMarker(children) || isSelfMarker(children) || isNotFoundMarker(children)) {
68
85
  result.push(children);
69
86
  }
70
87
  }
88
+
89
+ // child.children is a getter — read it INSIDE the JSX expression so Solid
90
+ // creates a reactive dependency. Pulling it into a variable freezes the
91
+ // value at template-build time and breaks Suspense fallback transitions
92
+ // (lazy() resolution).
93
+ function renderMatch(child) {
94
+ return child.fallback === undefined ? child.children : web.createComponent(solidJs.Suspense, {
95
+ get fallback() {
96
+ return child.fallback;
97
+ },
98
+ get children() {
99
+ return child.children;
100
+ }
101
+ });
102
+ }
103
+ function renderSelf(self) {
104
+ return self.fallback === undefined ? self.children : web.createComponent(solidJs.Suspense, {
105
+ get fallback() {
106
+ return self.fallback;
107
+ },
108
+ get children() {
109
+ return self.children;
110
+ }
111
+ });
112
+ }
113
+ function processMatchChild(child, routeName, nodeName) {
114
+ const {
115
+ segment,
116
+ exact
117
+ } = child;
118
+ const fullSegmentName = nodeName ? `${nodeName}.${segment}` : segment;
119
+ if (!isSegmentMatch(routeName, fullSegmentName, exact)) {
120
+ return null;
121
+ }
122
+ return renderMatch(child);
123
+ }
71
124
  function buildRenderList(elements, routeName, nodeName) {
125
+ let selfMarker = null;
72
126
  let notFoundChildren = null;
73
127
  let activeMatchFound = false;
74
128
  const rendered = [];
@@ -77,28 +131,25 @@ function buildRenderList(elements, routeName, nodeName) {
77
131
  notFoundChildren = child.children;
78
132
  continue;
79
133
  }
80
- if (activeMatchFound) {
134
+ if (isSelfMarker(child)) {
135
+ selfMarker ??= child;
81
136
  continue;
82
137
  }
83
- const {
84
- segment,
85
- exact,
86
- fallback
87
- } = child;
88
- const fullSegmentName = nodeName ? `${nodeName}.${segment}` : segment;
89
- if (!isSegmentMatch(routeName, fullSegmentName, exact)) {
138
+ if (activeMatchFound) {
90
139
  continue;
91
140
  }
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
- }));
141
+ const matchRendered = processMatchChild(child, routeName, nodeName);
142
+ if (matchRendered !== null) {
143
+ activeMatchFound = true;
144
+ rendered.push(matchRendered);
145
+ }
99
146
  }
100
- if (!activeMatchFound && routeName === core.UNKNOWN_ROUTE && notFoundChildren !== null) {
101
- rendered.push(notFoundChildren);
147
+ if (!activeMatchFound) {
148
+ if (selfMarker !== null && routeName === nodeName) {
149
+ rendered.push(renderSelf(selfMarker));
150
+ } else if (routeName === core.UNKNOWN_ROUTE && notFoundChildren !== null) {
151
+ rendered.push(notFoundChildren);
152
+ }
102
153
  }
103
154
  return rendered;
104
155
  }
@@ -157,6 +208,7 @@ function RouteViewRoot(props) {
157
208
  RouteViewRoot.displayName = "RouteView";
158
209
  const RouteView = Object.assign(RouteViewRoot, {
159
210
  Match,
211
+ Self,
160
212
  NotFound
161
213
  });
162
214
 
@@ -267,7 +319,7 @@ function removeAnnouncer() {
267
319
  document.querySelector(`[${ANNOUNCER_ATTR}]`)?.remove();
268
320
  }
269
321
  function resolveText(route, prefix, getCustomText, h1) {
270
- const h1Text = h1?.textContent.trim() ?? "";
322
+ const h1Text = (h1?.textContent ?? "").trim();
271
323
  const routeName = route.name.startsWith(INTERNAL_ROUTE_PREFIX) ? "" : route.name;
272
324
  const rawText = h1Text || document.title || routeName || globalThis.location.pathname;
273
325
  return `${prefix}${rawText}`;
@@ -285,14 +337,14 @@ function manageFocus(h1) {
285
337
  }
286
338
 
287
339
  const STORAGE_KEY = "real-router:scroll";
288
- const NOOP_INSTANCE = Object.freeze({
340
+ const NOOP_INSTANCE$1 = Object.freeze({
289
341
  destroy: () => {
290
342
  /* no-op */
291
343
  }
292
344
  });
293
345
  function createScrollRestoration(router, options) {
294
346
  if (typeof globalThis.window === "undefined") {
295
- return NOOP_INSTANCE;
347
+ return NOOP_INSTANCE$1;
296
348
  }
297
349
  const mode = options?.mode ?? "restore";
298
350
 
@@ -300,7 +352,7 @@ function createScrollRestoration(router, options) {
300
352
  // don't subscribe, don't register pagehide — leave the browser's native
301
353
  // auto-restore intact for the app to override if it wants to.
302
354
  if (mode === "manual") {
303
- return NOOP_INSTANCE;
355
+ return NOOP_INSTANCE$1;
304
356
  }
305
357
  const anchorEnabled = options?.anchorScrolling ?? true;
306
358
  const getContainer = options?.scrollContainer;
@@ -441,6 +493,128 @@ function canonicalReplacer(_key, val) {
441
493
  return val;
442
494
  }
443
495
 
496
+ const NOOP_INSTANCE = Object.freeze({
497
+ destroy: () => {
498
+ /* no-op */
499
+ }
500
+ });
501
+ function createViewTransitions(router) {
502
+ if (typeof document === "undefined" || typeof document.startViewTransition !== "function") {
503
+ return NOOP_INSTANCE;
504
+ }
505
+ let closeVT = null;
506
+ let currentVT = null;
507
+ // Tracks whether TRANSITION_SUCCESS fired for the current leave. Used to
508
+ // distinguish "benign cleanup abort" (router's async path aborts its own
509
+ // controller in a finally block after successful navigation) from "real
510
+ // cancellation" (concurrent navigate, guard rejection, dispose).
511
+ let successFired = false;
512
+ const resolveAndClear = () => {
513
+ closeVT?.();
514
+ closeVT = null;
515
+ };
516
+ const offLeave = router.subscribeLeave(({
517
+ signal
518
+ }) => {
519
+ // Reentrant abort: signal already aborted when we're called. Open no VT
520
+ // — router will fall through to TRANSITION_CANCELLED via isCurrentNav()
521
+ // after leave resolves. addEventListener("abort", ...) does not re-fire
522
+ // for past events, so skipping startViewTransition is the safe path.
523
+ if (signal.aborted) {
524
+ return;
525
+ }
526
+ successFired = false;
527
+ resolveAndClear();
528
+
529
+ // Return a Promise so the router awaits until the browser invokes
530
+ // updateCallback. This ensures old DOM snapshot is captured BEFORE the
531
+ // router commits the new state — giving correct exit→state→entry
532
+ // ordering (vs fire-and-forget, where URL changes before VT captures).
533
+ return new Promise(resolveLeave => {
534
+ // Capture the resolver synchronously BEFORE startViewTransition() is
535
+ // called. The browser invokes updateCallback in a later task, but
536
+ // router.subscribe (TRANSITION_SUCCESS) can fire before that. If we
537
+ // captured `resolve` inside the callback, subscribe would see closeVT
538
+ // still null and skip resolving — the deferred would hang for 4s
539
+ // until the VT API aborts with TimeoutError.
540
+ const deferred = new Promise(resolve => {
541
+ closeVT = resolve;
542
+ });
543
+ signal.addEventListener("abort", () => {
544
+ if (successFired) {
545
+ // Router's async path (#finishAsyncNavigation) aborts its own
546
+ // controller in a finally block AFTER completeTransition (and
547
+ // thus AFTER subscribe fired). This is cleanup, not
548
+ // cancellation — VT is progressing normally, do nothing.
549
+ return;
550
+ }
551
+
552
+ // Real cancellation (concurrent navigate, dispose). Resolve the
553
+ // deferred so updateCallback can complete, skip the VT so no
554
+ // stale animation leaks, and unblock the router if the abort
555
+ // fires before updateCallback was invoked.
556
+ resolveAndClear();
557
+ currentVT?.skipTransition?.();
558
+ resolveLeave();
559
+ }, {
560
+ once: true
561
+ });
562
+ try {
563
+ currentVT = document.startViewTransition(() => {
564
+ // Resolving here unblocks the router at the moment the browser
565
+ // enters updateCallback — by spec, old DOM snapshot is captured
566
+ // before this callback runs. Router now proceeds through
567
+ // activation guards and setState; the VT animation waits on
568
+ // `deferred`, which is resolved from router.subscribe after a
569
+ // task-queue tick (see NOTE on setTimeout below).
570
+ resolveLeave();
571
+ return deferred;
572
+ });
573
+ } catch {
574
+ // Defensive: spec says startViewTransition doesn't throw under
575
+ // normal conditions, but Chromium has had edge cases (detached
576
+ // document, extension interference). Clean up and unblock router.
577
+ resolveAndClear();
578
+ resolveLeave();
579
+ }
580
+ });
581
+ });
582
+ const offSuccess = router.subscribe(() => {
583
+ const resolver = closeVT;
584
+ successFired = true;
585
+ closeVT = null;
586
+ if (resolver === null) {
587
+ currentVT = null;
588
+ } else {
589
+ // CRITICAL: CANNOT use requestAnimationFrame here. When the router
590
+ // takes the async path (leave returned a Promise), subscribe fires
591
+ // AFTER the browser has already transitioned VT into the
592
+ // "update-callback-called" phase. In that phase Chromium sets
593
+ // rendering suppression to true, which ALSO blocks rAF callbacks.
594
+ // rAF would never fire → deferred never resolves → browser aborts
595
+ // vt.ready with TimeoutError after 4s (observed in Chromium).
596
+ //
597
+ // setTimeout runs on the task queue independent of the rendering
598
+ // pipeline, so it fires regardless of suppression. React's scheduler
599
+ // uses MessageChannel tasks, which are queued before our setTimeout,
600
+ // so the new DOM is committed by the time our callback runs.
601
+ setTimeout(() => {
602
+ resolver();
603
+ currentVT = null;
604
+ }, 0);
605
+ }
606
+ });
607
+ return {
608
+ destroy: () => {
609
+ offLeave();
610
+ offSuccess();
611
+ currentVT?.skipTransition?.();
612
+ currentVT = null;
613
+ resolveAndClear();
614
+ }
615
+ };
616
+ }
617
+
444
618
  function shouldNavigate(evt) {
445
619
  return evt.button === 0 && !evt.metaKey && !evt.altKey && !evt.ctrlKey && !evt.shiftKey;
446
620
  }
@@ -658,6 +832,206 @@ function useRouterTransition() {
658
832
  return createSignalFromSource(source);
659
833
  }
660
834
 
835
+ /**
836
+ * Subscribe to the router's leave-window with the universal guards baked
837
+ * in. Wraps `router.subscribeLeave` so consumers don't repeat the same
838
+ * boilerplate every time:
839
+ *
840
+ * - **Reentrant abort pre-check**: if `signal.aborted` is already `true`
841
+ * when the handler would run (rapid navigation superseded a slower
842
+ * one), the handler is skipped entirely. `signal.addEventListener(
843
+ * "abort", ...)` does not fire retroactively, so without this guard
844
+ * downstream cleanup would never trigger.
845
+ * - **Same-route skip**: by default, `route.name === nextRoute.name`
846
+ * short-circuits the handler — query-only navigations (sort, filter,
847
+ * pagination) skip the work. Opt out with `skipSameRoute: false`.
848
+ *
849
+ * Returns nothing — the subscription's lifecycle is bound to the
850
+ * component via `onCleanup`.
851
+ *
852
+ * If the handler returns a Promise, the router blocks on it. If the
853
+ * Promise resolves, navigation proceeds. If it rejects, the router emits
854
+ * `TRANSITION_CANCELLED`.
855
+ *
856
+ * **Handler reactivity (Solid)**: Solid components run **once** at mount;
857
+ * `handler` is captured in closure at the call site. If you need
858
+ * different behavior across renders, derive it from a signal inside the
859
+ * handler body — do not rely on swapping the handler reference.
860
+ *
861
+ * @example Animation
862
+ * ```tsx
863
+ * let ref: HTMLDivElement | undefined;
864
+ *
865
+ * useRouteExit(async ({ signal }) => {
866
+ * const el = ref;
867
+ * if (!el) return;
868
+ * el.classList.add("fade-out");
869
+ * const cleanup = () => el.classList.remove("fade-out");
870
+ * signal.addEventListener("abort", cleanup, { once: true });
871
+ * try {
872
+ * el.getBoundingClientRect(); // style flush
873
+ * await Promise.allSettled(el.getAnimations().map((a) => a.finished));
874
+ * } finally {
875
+ * cleanup();
876
+ * }
877
+ * });
878
+ * ```
879
+ *
880
+ * @example Auto-save form draft
881
+ * ```tsx
882
+ * useRouteExit(async ({ signal }) => {
883
+ * if (formState.dirty) await api.saveDraft(formState, { signal });
884
+ * });
885
+ * ```
886
+ *
887
+ * @example Reading rich transition metadata via `nextRoute.transition`
888
+ * ```tsx
889
+ * useRouteExit(({ route, nextRoute }) => {
890
+ * if (nextRoute.transition.segments.deactivated.includes("products")) {
891
+ * productCache.clear();
892
+ * }
893
+ * if (nextRoute.transition.redirected) {
894
+ * return;
895
+ * }
896
+ * });
897
+ * ```
898
+ */
899
+ function useRouteExit(handler, options) {
900
+ const router = useRouter();
901
+ const skipSameRoute = options?.skipSameRoute ?? true;
902
+ const off = router.subscribeLeave(({
903
+ route,
904
+ nextRoute,
905
+ signal
906
+ }) => {
907
+ if (skipSameRoute && route.name === nextRoute.name) {
908
+ return;
909
+ }
910
+
911
+ // Reentrant abort: signal is already aborted when listener fires
912
+ // (e.g. a newer navigate superseded this one before subscribeLeave
913
+ // even ran). addEventListener("abort", ...) does not fire
914
+ // retroactively, so we skip the handler entirely.
915
+ if (signal.aborted) {
916
+ return;
917
+ }
918
+ return handler({
919
+ route,
920
+ nextRoute,
921
+ signal
922
+ });
923
+ });
924
+ solidJs.onCleanup(off);
925
+ }
926
+
927
+ /**
928
+ * Fire `handler` once when the component mounts as a result of a
929
+ * navigation. Mirror of `useRouteExit` for the entry side.
930
+ *
931
+ * What this hook covers that an ad-hoc `createEffect` + `useRoute()`
932
+ * doesn't:
933
+ *
934
+ * - **Skip-initial**: handler is skipped when there is no
935
+ * `transition.from` (i.e. first-load mount). Most consumers want to
936
+ * fire side effects only on real navigations, not on hydration.
937
+ * - **Same-route skip** (default): handler is skipped when
938
+ * `route.transition.from === route.name`. Sort/filter/query-only
939
+ * navigations re-trigger the effect (because the `route` reference
940
+ * changes), but they are not "entries" in the animation / analytics
941
+ * sense — the component instance has stayed mounted throughout.
942
+ * Opt out with `skipSameRoute: false`.
943
+ * - **Mount-time `route` / `previousRoute` snapshot**: the handler
944
+ * receives the values that were live at the moment of effect
945
+ * activation, not the latest ones (which may have moved on if the
946
+ * user navigated again before the effect drained).
947
+ *
948
+ * **Handler reactivity (Solid)**: Solid components run **once** at mount;
949
+ * `handler` is captured in closure when the hook is called. If you need
950
+ * different behavior across renders, derive it from a signal inside the
951
+ * handler body — do not rely on swapping the handler reference.
952
+ *
953
+ * @example Direction-aware entry animation
954
+ * ```tsx
955
+ * useRouteEnter(({ route }) => {
956
+ * const direction = route.context.browser?.direction;
957
+ * ref?.classList.add(
958
+ * direction === "back" ? "slide-from-left" : "slide-from-right",
959
+ * );
960
+ * });
961
+ * ```
962
+ *
963
+ * @example Analytics page-enter event (skip-initial built-in)
964
+ * ```tsx
965
+ * useRouteEnter(({ route, previousRoute }) => {
966
+ * analytics.track("page_enter", {
967
+ * route: route.name,
968
+ * from: previousRoute.name,
969
+ * });
970
+ * });
971
+ * ```
972
+ *
973
+ * @example Reading rich transition metadata via `route.transition`
974
+ * ```tsx
975
+ * useRouteEnter(({ route }) => {
976
+ * if (route.transition.redirected) {
977
+ * showToast(`Redirected from ${route.transition.from}`);
978
+ * }
979
+ * if (route.transition.segments.activated.includes("products")) {
980
+ * // products subtree just became active
981
+ * }
982
+ * });
983
+ * ```
984
+ */
985
+ function useRouteEnter(handler, options) {
986
+ const routeState = useRoute();
987
+ const skipSameRoute = options?.skipSameRoute ?? true;
988
+ let lastHandledRoute = null;
989
+ solidJs.createEffect(() => {
990
+ const {
991
+ route,
992
+ previousRoute
993
+ } = routeState();
994
+
995
+ // Early-exit guards, top-down:
996
+ //
997
+ // - **Defensive**: `route` may be undefined during SSR or
998
+ // pre-start hydration. Not testable from vitest (tests start
999
+ // the router before render), so v8-ignored.
1000
+ // - **Skip-initial**: `state.transition.from` is undefined only
1001
+ // for the very first state committed by `router.start()`.
1002
+ // - **Skip-same-route**: query-only navigations have
1003
+ // `transition.from === route.name`. Opt-out via
1004
+ // `skipSameRoute: false`.
1005
+ // - **Defensive dedupe + missing `previousRoute`**: same `route`
1006
+ // ref between effect activations is unexpected on Solid (effects
1007
+ // run once per dependency change); `!previousRoute` is unreachable
1008
+ // once `transition.from` is set (the two are populated together by
1009
+ // core). Both kept for parity with React; v8-ignored.
1010
+ /* v8 ignore start */
1011
+ if (!route) {
1012
+ return;
1013
+ }
1014
+ /* v8 ignore stop */
1015
+ if (!route.transition.from) {
1016
+ return;
1017
+ }
1018
+ if (skipSameRoute && route.transition.from === route.name) {
1019
+ return;
1020
+ }
1021
+ /* v8 ignore start */
1022
+ if (lastHandledRoute === route || !previousRoute) {
1023
+ return;
1024
+ }
1025
+ /* v8 ignore stop */
1026
+
1027
+ lastHandledRoute = route;
1028
+ handler({
1029
+ route,
1030
+ previousRoute
1031
+ });
1032
+ });
1033
+ }
1034
+
661
1035
  function isRouteActive(linkRouteName, currentRouteName) {
662
1036
  return currentRouteName === linkRouteName || currentRouteName.startsWith(`${linkRouteName}.`);
663
1037
  }
@@ -680,6 +1054,15 @@ function RouterProvider(props) {
680
1054
  sr.destroy();
681
1055
  });
682
1056
  });
1057
+ solidJs.onMount(() => {
1058
+ if (!props.viewTransitions) {
1059
+ return;
1060
+ }
1061
+ const vt = createViewTransitions(props.router);
1062
+ solidJs.onCleanup(() => {
1063
+ vt.destroy();
1064
+ });
1065
+ });
683
1066
  const navigator = core.getNavigator(props.router);
684
1067
  const routeSource = sources.createRouteSource(props.router);
685
1068
  const routeSignal = createSignalFromSource(routeSource);
@@ -714,6 +1097,8 @@ exports.createStoreFromSource = createStoreFromSource;
714
1097
  exports.link = link;
715
1098
  exports.useNavigator = useNavigator;
716
1099
  exports.useRoute = useRoute;
1100
+ exports.useRouteEnter = useRouteEnter;
1101
+ exports.useRouteExit = useRouteExit;
717
1102
  exports.useRouteNode = useRouteNode;
718
1103
  exports.useRouteNodeStore = useRouteNodeStore;
719
1104
  exports.useRouteStore = useRouteStore;