@flight-framework/router 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -266,9 +266,32 @@ declare function prefetchWhenIdle(page: string, options?: PrefetchOptions): void
266
266
  * Router Hooks
267
267
  *
268
268
  * React hooks for accessing router state.
269
- * These require React to be available.
269
+ * Uses useSyncExternalStore for concurrent-safe external state (2026 best practice).
270
270
  */
271
271
 
272
+ /**
273
+ * Subscribe to pathname changes - for useSyncExternalStore
274
+ */
275
+ declare function subscribeToPathname(callback: () => void): () => void;
276
+ /**
277
+ * Get current pathname snapshot - for useSyncExternalStore
278
+ */
279
+ declare function getPathnameSnapshot(): string;
280
+ /**
281
+ * Get server snapshot - for useSyncExternalStore SSR
282
+ */
283
+ declare function getPathnameServerSnapshot(): string;
284
+ /**
285
+ * Create usePathname hook with provided React instance
286
+ * This pattern ensures proper bundler support for React hooks
287
+ *
288
+ * @example
289
+ * import { useSyncExternalStore } from 'react';
290
+ * import { createUsePathname } from '@flight-framework/router';
291
+ *
292
+ * const usePathname = createUsePathname(useSyncExternalStore);
293
+ */
294
+ declare function createUsePathname(useSyncExternalStore: <T>(subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => T, getServerSnapshot?: () => T) => T): () => string;
272
295
  declare let useParams: <T extends RouteParams = RouteParams>() => T;
273
296
  declare let useSearchParams: () => [URLSearchParams, (params: SearchParams | URLSearchParams) => void];
274
297
  declare let usePathname: () => string;
@@ -409,4 +432,4 @@ declare function observeForPrefetch(element: Element, href: string): () => void;
409
432
  */
410
433
  declare function setupIntentPrefetch(element: HTMLElement, href: string): () => void;
411
434
 
412
- export { Link, type LinkProps, type LoaderContext, type LoaderFunction, type NavigateOptions, type PrefetchOptions, PrefetchPageLinks, type PrefetchPriority, type PrefetchStrategy, type RouteDefinition, type RouteMatch, type RouteParams, RouterContext, type RouterContextValue, RouterProvider, type RouterProviderProps, type SearchParams, clearPrefetchCache, createLink, findRoute, generatePath, isActive, isPrefetched, matchRoute, navigate, observeForPrefetch, parseParams, prefetch, prefetchAll, prefetchPages, prefetchRoute, prefetchWhenIdle, redirect, setupIntentPrefetch, useLinkProps, useParams, usePathname, useRouter, useSearchParams };
435
+ export { Link, type LinkProps, type LoaderContext, type LoaderFunction, type NavigateOptions, type PrefetchOptions, PrefetchPageLinks, type PrefetchPriority, type PrefetchStrategy, type RouteDefinition, type RouteMatch, type RouteParams, RouterContext, type RouterContextValue, RouterProvider, type RouterProviderProps, type SearchParams, clearPrefetchCache, createLink, createUsePathname, findRoute, generatePath, getPathnameServerSnapshot, getPathnameSnapshot, isActive, isPrefetched, matchRoute, navigate, observeForPrefetch, parseParams, prefetch, prefetchAll, prefetchPages, prefetchRoute, prefetchWhenIdle, redirect, setupIntentPrefetch, subscribeToPathname, useLinkProps, useParams, usePathname, useRouter, useSearchParams };
package/dist/index.js CHANGED
@@ -39,6 +39,63 @@ function navigateTo(to, options = {}) {
39
39
  window.scrollTo({ top: 0, left: 0, behavior: "instant" });
40
40
  }
41
41
  }
42
+ function initRouter(options = {}) {
43
+ const { initialPath, basePath = "" } = options;
44
+ let path;
45
+ let searchParams;
46
+ if (isBrowser) {
47
+ path = window.location.pathname;
48
+ searchParams = new URLSearchParams(window.location.search);
49
+ } else {
50
+ path = initialPath || "/";
51
+ searchParams = new URLSearchParams();
52
+ }
53
+ if (basePath && path.startsWith(basePath)) {
54
+ path = path.slice(basePath.length) || "/";
55
+ }
56
+ currentContext = {
57
+ path,
58
+ searchParams,
59
+ navigate: navigateTo,
60
+ back: () => isBrowser && window.history.back(),
61
+ forward: () => isBrowser && window.history.forward()
62
+ };
63
+ if (isBrowser) {
64
+ window.addEventListener("popstate", () => {
65
+ updateContext({
66
+ path: window.location.pathname,
67
+ searchParams: new URLSearchParams(window.location.search)
68
+ });
69
+ });
70
+ const originalPushState = history.pushState.bind(history);
71
+ const originalReplaceState = history.replaceState.bind(history);
72
+ history.pushState = function(state, unused, url) {
73
+ originalPushState(state, unused, url);
74
+ if (url) {
75
+ const newUrl = new URL(url.toString(), window.location.origin);
76
+ updateContext({
77
+ path: newUrl.pathname,
78
+ searchParams: newUrl.searchParams
79
+ });
80
+ }
81
+ };
82
+ history.replaceState = function(state, unused, url) {
83
+ originalReplaceState(state, unused, url);
84
+ if (url) {
85
+ const newUrl = new URL(url.toString(), window.location.origin);
86
+ updateContext({
87
+ path: newUrl.pathname,
88
+ searchParams: newUrl.searchParams
89
+ });
90
+ }
91
+ };
92
+ }
93
+ }
94
+ var initialized = false;
95
+ if (isBrowser && !initialized) {
96
+ initialized = true;
97
+ initRouter();
98
+ }
42
99
  var RouterContext = null;
43
100
  var RouterProvider = null;
44
101
  var useRouter = getRouterContext;
@@ -493,15 +550,65 @@ function prefetchWhenIdle(page, options = {}) {
493
550
 
494
551
  // src/hooks.ts
495
552
  var isBrowser5 = typeof window !== "undefined";
553
+ var pathSubscribers = /* @__PURE__ */ new Set();
554
+ function notifyPathChange() {
555
+ pathSubscribers.forEach((fn) => fn());
556
+ }
557
+ var historyIntercepted = false;
558
+ function interceptHistory() {
559
+ if (!isBrowser5 || historyIntercepted) return;
560
+ historyIntercepted = true;
561
+ const originalPushState = history.pushState.bind(history);
562
+ const originalReplaceState = history.replaceState.bind(history);
563
+ history.pushState = function(state, unused, url) {
564
+ originalPushState(state, unused, url);
565
+ notifyPathChange();
566
+ };
567
+ history.replaceState = function(state, unused, url) {
568
+ originalReplaceState(state, unused, url);
569
+ notifyPathChange();
570
+ };
571
+ window.addEventListener("popstate", notifyPathChange);
572
+ }
573
+ if (isBrowser5) {
574
+ interceptHistory();
575
+ }
576
+ function subscribeToPathname(callback) {
577
+ pathSubscribers.add(callback);
578
+ return () => pathSubscribers.delete(callback);
579
+ }
580
+ function getPathnameSnapshot() {
581
+ return isBrowser5 ? window.location.pathname : "/";
582
+ }
583
+ function getPathnameServerSnapshot() {
584
+ return "/";
585
+ }
586
+ function createUsePathname(useSyncExternalStore) {
587
+ return function usePathname2() {
588
+ return useSyncExternalStore(
589
+ subscribeToPathname,
590
+ getPathnameSnapshot,
591
+ getPathnameServerSnapshot
592
+ );
593
+ };
594
+ }
496
595
  var useParams = () => ({});
497
596
  var useSearchParams = () => [new URLSearchParams(), () => {
498
597
  }];
499
- var usePathname = () => "/";
598
+ var usePathname = () => {
599
+ if (isBrowser5) {
600
+ return window.location.pathname;
601
+ }
602
+ return "/";
603
+ };
500
604
  if (typeof globalThis !== "undefined") {
501
605
  try {
502
606
  const React = globalThis.React;
607
+ if (React?.useSyncExternalStore) {
608
+ usePathname = createUsePathname(React.useSyncExternalStore);
609
+ }
503
610
  if (React?.useState) {
504
- const { useState, useEffect, useCallback, useMemo } = React;
611
+ const { useState, useEffect, useCallback } = React;
505
612
  useParams = function useFlightParams() {
506
613
  const [params, setParams] = useState({});
507
614
  useEffect(() => {
@@ -545,19 +652,6 @@ if (typeof globalThis !== "undefined") {
545
652
  }, []);
546
653
  return [searchParams, setSearchParams];
547
654
  };
548
- usePathname = function useFlightPathname() {
549
- const { path } = getRouterContext();
550
- const [pathname, setPathname] = useState(path);
551
- useEffect(() => {
552
- if (!isBrowser5) return;
553
- const handleChange = () => {
554
- setPathname(window.location.pathname);
555
- };
556
- window.addEventListener("popstate", handleChange);
557
- return () => window.removeEventListener("popstate", handleChange);
558
- }, []);
559
- return pathname;
560
- };
561
655
  }
562
656
  } catch {
563
657
  }
@@ -644,8 +738,11 @@ export {
644
738
  RouterProvider,
645
739
  clearPrefetchCache,
646
740
  createLink,
741
+ createUsePathname,
647
742
  findRoute,
648
743
  generatePath,
744
+ getPathnameServerSnapshot,
745
+ getPathnameSnapshot,
649
746
  isActive,
650
747
  isPrefetched,
651
748
  matchRoute,
@@ -659,6 +756,7 @@ export {
659
756
  prefetchWhenIdle,
660
757
  redirect,
661
758
  setupIntentPrefetch,
759
+ subscribeToPathname,
662
760
  useLinkProps,
663
761
  useParams,
664
762
  usePathname,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flight-framework/router",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "Agnostic client-side routing primitives for Flight Framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -45,4 +45,4 @@
45
45
  "optional": true
46
46
  }
47
47
  }
48
- }
48
+ }