@real-router/sources 0.4.2 → 0.4.4

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/types.ts","../../src/createRouteSource.ts","../../src/createRouteNodeSource.ts","../../src/createActiveRouteSource.ts","../../src/createTransitionSource.ts","../../src/createErrorSource.ts"],"mappings":";;;UAEiB,aAAA;EACf,KAAA,EAAO,KAAA;EACP,aAAA,EAAe,KAAA;AAAA;AAAA,UAGA,iBAAA;EACf,KAAA,EAAO,KAAA;EACP,aAAA,EAAe,KAAA;AAAA;AAAA,UAGA,YAAA;EACf,SAAA,GAAY,QAAA;EACZ,WAAA,QAAmB,CAAA;EACnB,OAAA;AAAA;AAAA,UAGe,wBAAA;EACf,MAAA;EACA,iBAAA;AAAA;AAAA,UAGe,wBAAA;EACf,eAAA;EACA,eAAA;EACA,OAAA,EAAS,KAAA;EACT,SAAA,EAAW,KAAA;AAAA;AAAA,UAGI,mBAAA;EACf,KAAA,EAAO,WAAA;EACP,OAAA,EAAS,KAAA;EACT,SAAA,EAAW,KAAA;EACX,OAAA;AAAA;;;;AAhCF;;;;;;iBCWgB,iBAAA,CAAkB,MAAA,EAAQ,MAAA,GAAS,YAAA,CAAa,aAAA;;;;ADXhE;;;;;;iBEYgB,qBAAA,CACd,MAAA,EAAQ,MAAA,EACR,QAAA,WACC,YAAA,CAAa,iBAAA;;;iBCVA,uBAAA,CACd,MAAA,EAAQ,MAAA,EACR,SAAA,UACA,MAAA,GAAS,MAAA,EACT,OAAA,GAAU,wBAAA,GACT,YAAA;;;iBCIa,sBAAA,CACd,MAAA,EAAQ,MAAA,GACP,YAAA,CAAa,wBAAA;;;iBCHA,iBAAA,CACd,MAAA,EAAQ,MAAA,GACP,YAAA,CAAa,mBAAA"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/types.ts","../../src/createRouteSource.ts","../../src/createRouteNodeSource.ts","../../src/createActiveRouteSource.ts","../../src/createTransitionSource.ts","../../src/createErrorSource.ts"],"mappings":";;;UAEiB,aAAA;EACf,KAAA,EAAO,KAAA;EACP,aAAA,EAAe,KAAA;AAAA;AAAA,UAGA,iBAAA;EACf,KAAA,EAAO,KAAA;EACP,aAAA,EAAe,KAAA;AAAA;AAAA,UAGA,YAAA;EACf,SAAA,GAAY,QAAA;EACZ,WAAA,QAAmB,CAAA;EACnB,OAAA;AAAA;AAAA,UAGe,wBAAA;EACf,MAAA;EACA,iBAAA;AAAA;AAAA,UAGe,wBAAA;EACf,eAAA;EACA,eAAA;EACA,OAAA,EAAS,KAAA;EACT,SAAA,EAAW,KAAA;AAAA;AAAA,UAGI,mBAAA;EACf,KAAA,EAAO,WAAA;EACP,OAAA,EAAS,KAAA;EACT,SAAA,EAAW,KAAA;EACX,OAAA;AAAA;;;;AAhCF;;;;;;iBCWgB,iBAAA,CAAkB,MAAA,EAAQ,MAAA,GAAS,YAAA,CAAa,aAAA;;;;ADXhE;;;;;;iBEYgB,qBAAA,CACd,MAAA,EAAQ,MAAA,EACR,QAAA,WACC,YAAA,CAAa,iBAAA;;;iBCVA,uBAAA,CACd,MAAA,EAAQ,MAAA,EACR,SAAA,UACA,MAAA,GAAS,MAAA,EACT,OAAA,GAAU,wBAAA,GACT,YAAA;;;iBCIa,sBAAA,CACd,MAAA,EAAQ,MAAA,GACP,YAAA,CAAa,wBAAA;;;iBCHA,iBAAA,CACd,MAAA,EAAQ,MAAA,GACP,YAAA,CAAa,mBAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@real-router/sources",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "type": "commonjs",
5
5
  "description": "Framework-agnostic subscription layer for Real-Router state",
6
6
  "main": "./dist/cjs/index.js",
@@ -8,7 +8,6 @@
8
8
  "types": "./dist/esm/index.d.mts",
9
9
  "exports": {
10
10
  ".": {
11
- "development": "./src/index.ts",
12
11
  "types": {
13
12
  "import": "./dist/esm/index.d.mts",
14
13
  "require": "./dist/cjs/index.d.ts"
@@ -18,7 +17,8 @@
18
17
  }
19
18
  },
20
19
  "files": [
21
- "dist"
20
+ "dist",
21
+ "src"
22
22
  ],
23
23
  "repository": {
24
24
  "type": "git",
@@ -42,8 +42,8 @@
42
42
  "homepage": "https://github.com/greydragon888/real-router",
43
43
  "sideEffects": false,
44
44
  "dependencies": {
45
- "@real-router/core": "^0.45.1",
46
- "@real-router/route-utils": "^0.1.12"
45
+ "@real-router/core": "^0.46.0",
46
+ "@real-router/route-utils": "^0.1.14"
47
47
  },
48
48
  "devDependencies": {
49
49
  "mitata": "1.0.34"
@@ -53,7 +53,7 @@
53
53
  "build": "tsdown --config-loader unrun",
54
54
  "type-check": "tsc --noEmit",
55
55
  "lint": "eslint --cache --ext .ts src/ tests/ --fix --max-warnings 0 --no-error-on-unmatched-pattern",
56
- "lint:package": "bash ../../scripts/publint-filter.sh",
56
+ "lint:package": "publint",
57
57
  "lint:types": "attw --pack .",
58
58
  "test:properties": "vitest --config vitest.config.properties.mts --run",
59
59
  "test:stress": "vitest run --config vitest.config.stress.mts",
@@ -0,0 +1,76 @@
1
+ export interface BaseSourceOptions {
2
+ onFirstSubscribe?: () => void;
3
+ onLastUnsubscribe?: () => void;
4
+ onDestroy?: () => void;
5
+ }
6
+
7
+ export class BaseSource<T> {
8
+ #currentSnapshot: T;
9
+ #destroyed = false;
10
+
11
+ readonly #listeners = new Set<() => void>();
12
+ readonly #onFirstSubscribe: (() => void) | undefined;
13
+ readonly #onLastUnsubscribe: (() => void) | undefined;
14
+ readonly #onDestroy: (() => void) | undefined;
15
+
16
+ constructor(initialSnapshot: T, options?: BaseSourceOptions) {
17
+ this.#currentSnapshot = initialSnapshot;
18
+ this.#onFirstSubscribe = options?.onFirstSubscribe;
19
+ this.#onLastUnsubscribe = options?.onLastUnsubscribe;
20
+ this.#onDestroy = options?.onDestroy;
21
+
22
+ this.subscribe = this.subscribe.bind(this);
23
+ this.getSnapshot = this.getSnapshot.bind(this);
24
+ this.destroy = this.destroy.bind(this);
25
+ }
26
+
27
+ subscribe(listener: () => void): () => void {
28
+ if (this.#destroyed) {
29
+ return () => {};
30
+ }
31
+
32
+ if (this.#listeners.size === 0 && this.#onFirstSubscribe) {
33
+ this.#onFirstSubscribe();
34
+ }
35
+
36
+ this.#listeners.add(listener);
37
+
38
+ return () => {
39
+ this.#listeners.delete(listener);
40
+
41
+ if (
42
+ !this.#destroyed &&
43
+ this.#listeners.size === 0 &&
44
+ this.#onLastUnsubscribe
45
+ ) {
46
+ this.#onLastUnsubscribe();
47
+ }
48
+ };
49
+ }
50
+
51
+ getSnapshot(): T {
52
+ return this.#currentSnapshot;
53
+ }
54
+
55
+ updateSnapshot(snapshot: T): void {
56
+ /* v8 ignore next 2 -- @preserve: defensive guard unreachable via public API (destroy() removes router subscription first) */
57
+ if (this.#destroyed) {
58
+ return;
59
+ }
60
+
61
+ this.#currentSnapshot = snapshot;
62
+ this.#listeners.forEach((listener) => {
63
+ listener();
64
+ });
65
+ }
66
+
67
+ destroy(): void {
68
+ if (this.#destroyed) {
69
+ return;
70
+ }
71
+
72
+ this.#destroyed = true;
73
+ this.#onDestroy?.();
74
+ this.#listeners.clear();
75
+ }
76
+ }
@@ -0,0 +1,44 @@
1
+ import { stabilizeState } from "./stabilizeState.js";
2
+
3
+ import type { RouteNodeSnapshot } from "./types.js";
4
+ import type { Router, SubscribeState } from "@real-router/core";
5
+
6
+ export function computeSnapshot(
7
+ currentSnapshot: RouteNodeSnapshot,
8
+ router: Router,
9
+ nodeName: string,
10
+ next?: SubscribeState,
11
+ ): RouteNodeSnapshot {
12
+ const currentRoute = next?.route ?? router.getState();
13
+ const previousRoute = next?.previousRoute;
14
+
15
+ const isNodeActive =
16
+ nodeName === "" ||
17
+ (currentRoute !== undefined &&
18
+ (currentRoute.name === nodeName ||
19
+ currentRoute.name.startsWith(`${nodeName}.`)));
20
+
21
+ const route = isNodeActive ? currentRoute : undefined;
22
+
23
+ if (
24
+ route === currentSnapshot.route &&
25
+ previousRoute === currentSnapshot.previousRoute
26
+ ) {
27
+ return currentSnapshot;
28
+ }
29
+
30
+ const newRoute = stabilizeState(currentSnapshot.route, route);
31
+ const newPreviousRoute = stabilizeState(
32
+ currentSnapshot.previousRoute,
33
+ previousRoute,
34
+ );
35
+
36
+ if (
37
+ newRoute === currentSnapshot.route &&
38
+ newPreviousRoute === currentSnapshot.previousRoute
39
+ ) {
40
+ return currentSnapshot;
41
+ }
42
+
43
+ return { route: newRoute, previousRoute: newPreviousRoute };
44
+ }
@@ -0,0 +1,53 @@
1
+ import { areRoutesRelated } from "@real-router/route-utils";
2
+
3
+ import { BaseSource } from "./BaseSource";
4
+
5
+ import type { ActiveRouteSourceOptions, RouterSource } from "./types.js";
6
+ import type { Params, Router } from "@real-router/core";
7
+
8
+ export function createActiveRouteSource(
9
+ router: Router,
10
+ routeName: string,
11
+ params?: Params,
12
+ options?: ActiveRouteSourceOptions,
13
+ ): RouterSource<boolean> {
14
+ const strict = options?.strict ?? false;
15
+ const ignoreQueryParams = options?.ignoreQueryParams ?? true;
16
+
17
+ const initialValue = router.isActiveRoute(
18
+ routeName,
19
+ params,
20
+ strict,
21
+ ignoreQueryParams,
22
+ );
23
+
24
+ const source = new BaseSource(initialValue, {
25
+ onDestroy: () => {
26
+ unsubscribe();
27
+ },
28
+ });
29
+
30
+ // Eager connection: subscribe to router immediately
31
+ const unsubscribe = router.subscribe((next) => {
32
+ const isNewRelated = areRoutesRelated(routeName, next.route.name);
33
+ const isPrevRelated =
34
+ next.previousRoute &&
35
+ areRoutesRelated(routeName, next.previousRoute.name);
36
+
37
+ if (!isNewRelated && !isPrevRelated) {
38
+ return;
39
+ }
40
+
41
+ // If new route is not related, we know the route is inactive —
42
+ // avoid calling isActiveRoute for the optimization
43
+ const newValue = isNewRelated
44
+ ? router.isActiveRoute(routeName, params, strict, ignoreQueryParams)
45
+ : false;
46
+
47
+ if (!Object.is(source.getSnapshot(), newValue)) {
48
+ source.updateSnapshot(newValue);
49
+ }
50
+ });
51
+
52
+ return source;
53
+ }
@@ -0,0 +1,66 @@
1
+ import { events } from "@real-router/core";
2
+ import { getPluginApi } from "@real-router/core/api";
3
+
4
+ import { BaseSource } from "./BaseSource";
5
+
6
+ import type { RouterErrorSnapshot, RouterSource } from "./types.js";
7
+ import type { Router, State, RouterError } from "@real-router/core";
8
+
9
+ const INITIAL_SNAPSHOT: RouterErrorSnapshot = {
10
+ error: null,
11
+ toRoute: null,
12
+ fromRoute: null,
13
+ version: 0,
14
+ };
15
+
16
+ export function createErrorSource(
17
+ router: Router,
18
+ ): RouterSource<RouterErrorSnapshot> {
19
+ let errorVersion = 0;
20
+
21
+ const source = new BaseSource(INITIAL_SNAPSHOT, {
22
+ onDestroy: () => {
23
+ unsubs.forEach((unsub) => {
24
+ unsub();
25
+ });
26
+ },
27
+ });
28
+
29
+ const api = getPluginApi(router);
30
+
31
+ // Eager connection: subscribe to router events immediately
32
+ const unsubs = [
33
+ api.addEventListener(
34
+ events.TRANSITION_ERROR,
35
+ (
36
+ toState: State | undefined,
37
+ fromState: State | undefined,
38
+ err: RouterError,
39
+ ) => {
40
+ errorVersion++;
41
+ source.updateSnapshot({
42
+ error: err,
43
+ toRoute: toState ?? null,
44
+ /* v8 ignore next -- @preserve: fromState undefined only during start() error; unreachable via navigate() */
45
+ fromRoute: fromState ?? null,
46
+ version: errorVersion,
47
+ });
48
+ },
49
+ ),
50
+ api.addEventListener(events.TRANSITION_SUCCESS, () => {
51
+ // Skip if no error — avoids unnecessary re-renders.
52
+ // BaseSource.updateSnapshot() always notifies listeners (new object = new ref),
53
+ // and useSyncExternalStore compares via Object.is().
54
+ if (source.getSnapshot().error !== null) {
55
+ source.updateSnapshot({
56
+ error: null,
57
+ toRoute: null,
58
+ fromRoute: null,
59
+ version: errorVersion,
60
+ });
61
+ }
62
+ }),
63
+ ];
64
+
65
+ return source;
66
+ }
@@ -0,0 +1,76 @@
1
+ import { BaseSource } from "./BaseSource";
2
+ import { computeSnapshot } from "./computeSnapshot.js";
3
+ import { getCachedShouldUpdate } from "./shouldUpdateCache.js";
4
+
5
+ import type { RouteNodeSnapshot, RouterSource } from "./types.js";
6
+ import type { Router } from "@real-router/core";
7
+
8
+ /**
9
+ * Creates a source scoped to a specific route node.
10
+ *
11
+ * Uses a lazy-connection pattern: the router subscription is created when the
12
+ * first listener subscribes and removed when the last listener unsubscribes.
13
+ * This is compatible with React's useSyncExternalStore and Strict Mode.
14
+ */
15
+ export function createRouteNodeSource(
16
+ router: Router,
17
+ nodeName: string,
18
+ ): RouterSource<RouteNodeSnapshot> {
19
+ let routerUnsubscribe: (() => void) | null = null;
20
+
21
+ const shouldUpdate = getCachedShouldUpdate(router, nodeName);
22
+
23
+ const initialSnapshot: RouteNodeSnapshot = {
24
+ route: undefined,
25
+ previousRoute: undefined,
26
+ };
27
+
28
+ const disconnect = (): void => {
29
+ const unsub = routerUnsubscribe;
30
+
31
+ routerUnsubscribe = null;
32
+ unsub?.();
33
+ };
34
+
35
+ const source = new BaseSource<RouteNodeSnapshot>(
36
+ computeSnapshot(initialSnapshot, router, nodeName),
37
+ {
38
+ onFirstSubscribe: () => {
39
+ // Reconcile snapshot with current router state before connecting.
40
+ // Covers reconnection after Activity hide/show cycles where the
41
+ // source was disconnected and missed navigation events.
42
+ const reconciled = computeSnapshot(
43
+ source.getSnapshot(),
44
+ router,
45
+ nodeName,
46
+ );
47
+
48
+ if (!Object.is(reconciled, source.getSnapshot())) {
49
+ source.updateSnapshot(reconciled);
50
+ }
51
+
52
+ // Connect to router on first subscription
53
+ routerUnsubscribe = router.subscribe((next) => {
54
+ if (!shouldUpdate(next.route, next.previousRoute)) {
55
+ return;
56
+ }
57
+
58
+ const newSnapshot = computeSnapshot(
59
+ source.getSnapshot(),
60
+ router,
61
+ nodeName,
62
+ next,
63
+ );
64
+
65
+ if (!Object.is(source.getSnapshot(), newSnapshot)) {
66
+ source.updateSnapshot(newSnapshot);
67
+ }
68
+ });
69
+ },
70
+ onLastUnsubscribe: disconnect,
71
+ onDestroy: disconnect,
72
+ },
73
+ );
74
+
75
+ return source;
76
+ }
@@ -0,0 +1,56 @@
1
+ import { BaseSource } from "./BaseSource";
2
+ import { stabilizeState } from "./stabilizeState.js";
3
+
4
+ import type { RouteSnapshot, RouterSource } from "./types.js";
5
+ import type { Router } from "@real-router/core";
6
+
7
+ /**
8
+ * Creates a source for the full route state.
9
+ *
10
+ * Uses a lazy-connection pattern: the router subscription is created when the
11
+ * first listener subscribes and removed when the last listener unsubscribes.
12
+ * This is compatible with React's useSyncExternalStore and Strict Mode.
13
+ */
14
+ export function createRouteSource(router: Router): RouterSource<RouteSnapshot> {
15
+ let routerUnsubscribe: (() => void) | null = null;
16
+
17
+ const disconnect = (): void => {
18
+ const unsub = routerUnsubscribe;
19
+
20
+ routerUnsubscribe = null;
21
+ unsub?.();
22
+ };
23
+
24
+ const source = new BaseSource<RouteSnapshot>(
25
+ {
26
+ route: router.getState(),
27
+ previousRoute: undefined,
28
+ },
29
+ {
30
+ onFirstSubscribe: () => {
31
+ routerUnsubscribe = router.subscribe((next) => {
32
+ const prev = source.getSnapshot();
33
+ const newRoute = stabilizeState(prev.route, next.route);
34
+ const newPreviousRoute = stabilizeState(
35
+ prev.previousRoute,
36
+ next.previousRoute,
37
+ );
38
+
39
+ if (
40
+ newRoute !== prev.route ||
41
+ newPreviousRoute !== prev.previousRoute
42
+ ) {
43
+ source.updateSnapshot({
44
+ route: newRoute,
45
+ previousRoute: newPreviousRoute,
46
+ });
47
+ }
48
+ });
49
+ },
50
+ onLastUnsubscribe: disconnect,
51
+ onDestroy: disconnect,
52
+ },
53
+ );
54
+
55
+ return source;
56
+ }
@@ -0,0 +1,76 @@
1
+ import { events } from "@real-router/core";
2
+ import { getPluginApi } from "@real-router/core/api";
3
+
4
+ import { BaseSource } from "./BaseSource";
5
+ import { stabilizeState } from "./stabilizeState.js";
6
+
7
+ import type { RouterTransitionSnapshot, RouterSource } from "./types.js";
8
+ import type { Router, State } from "@real-router/core";
9
+
10
+ const IDLE_SNAPSHOT: RouterTransitionSnapshot = {
11
+ isTransitioning: false,
12
+ isLeaveApproved: false,
13
+ toRoute: null,
14
+ fromRoute: null,
15
+ };
16
+
17
+ export function createTransitionSource(
18
+ router: Router,
19
+ ): RouterSource<RouterTransitionSnapshot> {
20
+ const source = new BaseSource(IDLE_SNAPSHOT, {
21
+ onDestroy: () => {
22
+ unsubs.forEach((unsub) => {
23
+ unsub();
24
+ });
25
+ },
26
+ });
27
+
28
+ const api = getPluginApi(router);
29
+
30
+ const resetToIdle = (): void => {
31
+ source.updateSnapshot(IDLE_SNAPSHOT);
32
+ };
33
+
34
+ // Eager connection: subscribe to router events immediately
35
+ const unsubs = [
36
+ api.addEventListener(
37
+ events.TRANSITION_START,
38
+ (toState: State, fromState?: State) => {
39
+ const prev = source.getSnapshot();
40
+ const newToRoute = stabilizeState(prev.toRoute, toState);
41
+ const newFromRoute = stabilizeState(prev.fromRoute, fromState ?? null);
42
+
43
+ if (
44
+ !prev.isTransitioning ||
45
+ newToRoute !== prev.toRoute ||
46
+ newFromRoute !== prev.fromRoute
47
+ ) {
48
+ source.updateSnapshot({
49
+ isTransitioning: true,
50
+ isLeaveApproved: false,
51
+ toRoute: newToRoute,
52
+ fromRoute: newFromRoute,
53
+ });
54
+ }
55
+ },
56
+ ),
57
+ api.addEventListener(
58
+ events.TRANSITION_LEAVE_APPROVE,
59
+ (toState: State, fromState?: State) => {
60
+ const prev = source.getSnapshot();
61
+
62
+ source.updateSnapshot({
63
+ isTransitioning: true,
64
+ isLeaveApproved: true,
65
+ toRoute: stabilizeState(prev.toRoute, toState),
66
+ fromRoute: stabilizeState(prev.fromRoute, fromState ?? null),
67
+ });
68
+ },
69
+ ),
70
+ api.addEventListener(events.TRANSITION_SUCCESS, resetToIdle),
71
+ api.addEventListener(events.TRANSITION_ERROR, resetToIdle),
72
+ api.addEventListener(events.TRANSITION_CANCEL, resetToIdle),
73
+ ];
74
+
75
+ return source;
76
+ }
package/src/index.ts ADDED
@@ -0,0 +1,18 @@
1
+ export type {
2
+ RouterSource,
3
+ RouteSnapshot,
4
+ RouteNodeSnapshot,
5
+ ActiveRouteSourceOptions,
6
+ RouterTransitionSnapshot,
7
+ RouterErrorSnapshot,
8
+ } from "./types.js";
9
+
10
+ export { createRouteSource } from "./createRouteSource";
11
+
12
+ export { createRouteNodeSource } from "./createRouteNodeSource";
13
+
14
+ export { createActiveRouteSource } from "./createActiveRouteSource";
15
+
16
+ export { createTransitionSource } from "./createTransitionSource";
17
+
18
+ export { createErrorSource } from "./createErrorSource";
@@ -0,0 +1,27 @@
1
+ import type { Router, State } from "@real-router/core";
2
+
3
+ const shouldUpdateCache = new WeakMap<
4
+ Router,
5
+ Map<string, (toState: State, fromState?: State) => boolean>
6
+ >();
7
+
8
+ export function getCachedShouldUpdate(
9
+ router: Router,
10
+ nodeName: string,
11
+ ): (toState: State, fromState?: State) => boolean {
12
+ let routerCache = shouldUpdateCache.get(router);
13
+
14
+ if (!routerCache) {
15
+ routerCache = new Map();
16
+ shouldUpdateCache.set(router, routerCache);
17
+ }
18
+
19
+ let fn = routerCache.get(nodeName);
20
+
21
+ if (!fn) {
22
+ fn = router.shouldUpdateNode(nodeName);
23
+ routerCache.set(nodeName, fn);
24
+ }
25
+
26
+ return fn;
27
+ }
@@ -0,0 +1,31 @@
1
+ import type { State } from "@real-router/core";
2
+
3
+ /**
4
+ * State-aware stabilization for route snapshots.
5
+ *
6
+ * Compares `path` — the canonical representation of rendering-relevant
7
+ * State fields (name + params). When path matches, returns `prev`
8
+ * (preserving reference), enabling frameworks to skip re-renders.
9
+ *
10
+ * Ignores `meta` (internal: auto-increment id) and `transition`
11
+ * (reference data: from, segments, reload) — they don't affect
12
+ * what is rendered.
13
+ *
14
+ * Accepts `null` for compatibility with `RouterTransitionSnapshot`
15
+ * (toRoute/fromRoute are `State | null`).
16
+ *
17
+ * @internal Not exported from package public API.
18
+ */
19
+ export function stabilizeState<T extends State | null | undefined>(
20
+ prev: T,
21
+ next: T,
22
+ ): T {
23
+ if (prev === next) {
24
+ return prev;
25
+ }
26
+ if (prev?.path !== next?.path) {
27
+ return next;
28
+ }
29
+
30
+ return prev;
31
+ }
package/src/types.ts ADDED
@@ -0,0 +1,36 @@
1
+ import type { RouterError, State } from "@real-router/core";
2
+
3
+ export interface RouteSnapshot {
4
+ route: State | undefined;
5
+ previousRoute: State | undefined;
6
+ }
7
+
8
+ export interface RouteNodeSnapshot {
9
+ route: State | undefined;
10
+ previousRoute: State | undefined;
11
+ }
12
+
13
+ export interface RouterSource<T> {
14
+ subscribe: (listener: () => void) => () => void;
15
+ getSnapshot: () => T;
16
+ destroy: () => void;
17
+ }
18
+
19
+ export interface ActiveRouteSourceOptions {
20
+ strict?: boolean;
21
+ ignoreQueryParams?: boolean;
22
+ }
23
+
24
+ export interface RouterTransitionSnapshot {
25
+ isTransitioning: boolean;
26
+ isLeaveApproved: boolean;
27
+ toRoute: State | null;
28
+ fromRoute: State | null;
29
+ }
30
+
31
+ export interface RouterErrorSnapshot {
32
+ error: RouterError | null;
33
+ toRoute: State | null;
34
+ fromRoute: State | null;
35
+ version: number;
36
+ }