@furystack/shades 13.2.2 → 15.0.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 (146) hide show
  1. package/CHANGELOG.md +157 -0
  2. package/esm/component-factory.spec.d.ts.map +1 -1
  3. package/esm/component-factory.spec.js +2 -2
  4. package/esm/component-factory.spec.js.map +1 -1
  5. package/esm/components/define-nested-routes.d.ts +27 -0
  6. package/esm/components/define-nested-routes.d.ts.map +1 -0
  7. package/esm/components/define-nested-routes.js +26 -0
  8. package/esm/components/define-nested-routes.js.map +1 -0
  9. package/esm/components/define-nested-routes.spec.d.ts +2 -0
  10. package/esm/components/define-nested-routes.spec.d.ts.map +1 -0
  11. package/esm/components/define-nested-routes.spec.js +81 -0
  12. package/esm/components/define-nested-routes.spec.js.map +1 -0
  13. package/esm/components/index.d.ts +3 -3
  14. package/esm/components/index.d.ts.map +1 -1
  15. package/esm/components/index.js +3 -3
  16. package/esm/components/index.js.map +1 -1
  17. package/esm/components/nested-hooks.d.ts +62 -0
  18. package/esm/components/nested-hooks.d.ts.map +1 -0
  19. package/esm/components/nested-hooks.js +73 -0
  20. package/esm/components/nested-hooks.js.map +1 -0
  21. package/esm/components/nested-hooks.spec.d.ts +2 -0
  22. package/esm/components/nested-hooks.spec.d.ts.map +1 -0
  23. package/esm/components/nested-hooks.spec.js +211 -0
  24. package/esm/components/nested-hooks.spec.js.map +1 -0
  25. package/esm/components/nested-navigate.d.ts +52 -21
  26. package/esm/components/nested-navigate.d.ts.map +1 -1
  27. package/esm/components/nested-navigate.js +32 -17
  28. package/esm/components/nested-navigate.js.map +1 -1
  29. package/esm/components/nested-navigate.spec.js +109 -19
  30. package/esm/components/nested-navigate.spec.js.map +1 -1
  31. package/esm/components/nested-replace.d.ts +36 -0
  32. package/esm/components/nested-replace.d.ts.map +1 -0
  33. package/esm/components/nested-replace.js +38 -0
  34. package/esm/components/nested-replace.js.map +1 -0
  35. package/esm/components/nested-replace.spec.d.ts +2 -0
  36. package/esm/components/nested-replace.spec.d.ts.map +1 -0
  37. package/esm/components/nested-replace.spec.js +59 -0
  38. package/esm/components/nested-replace.spec.js.map +1 -0
  39. package/esm/components/nested-route-link.d.ts +36 -26
  40. package/esm/components/nested-route-link.d.ts.map +1 -1
  41. package/esm/components/nested-route-link.js +18 -20
  42. package/esm/components/nested-route-link.js.map +1 -1
  43. package/esm/components/nested-route-link.spec.js +67 -17
  44. package/esm/components/nested-route-link.spec.js.map +1 -1
  45. package/esm/components/nested-route-types.d.ts +79 -2
  46. package/esm/components/nested-route-types.d.ts.map +1 -1
  47. package/esm/components/nested-router.d.ts +77 -7
  48. package/esm/components/nested-router.d.ts.map +1 -1
  49. package/esm/components/nested-router.js +139 -13
  50. package/esm/components/nested-router.js.map +1 -1
  51. package/esm/components/nested-router.spec.js +283 -52
  52. package/esm/components/nested-router.spec.js.map +1 -1
  53. package/esm/services/location-service.d.ts +39 -42
  54. package/esm/services/location-service.d.ts.map +1 -1
  55. package/esm/services/location-service.js +115 -134
  56. package/esm/services/location-service.js.map +1 -1
  57. package/esm/services/location-service.spec.js +59 -24
  58. package/esm/services/location-service.spec.js.map +1 -1
  59. package/esm/services/route-match-service.d.ts +3 -2
  60. package/esm/services/route-match-service.d.ts.map +1 -1
  61. package/esm/services/route-match-service.js +13 -22
  62. package/esm/services/route-match-service.js.map +1 -1
  63. package/esm/services/route-match-service.spec.js +42 -49
  64. package/esm/services/route-match-service.spec.js.map +1 -1
  65. package/esm/services/route-meta-utils.d.ts +28 -6
  66. package/esm/services/route-meta-utils.d.ts.map +1 -1
  67. package/esm/services/route-meta-utils.js +18 -4
  68. package/esm/services/route-meta-utils.js.map +1 -1
  69. package/esm/services/route-meta-utils.spec.js +60 -5
  70. package/esm/services/route-meta-utils.spec.js.map +1 -1
  71. package/esm/services/screen-service.d.ts +13 -72
  72. package/esm/services/screen-service.d.ts.map +1 -1
  73. package/esm/services/screen-service.js +45 -137
  74. package/esm/services/screen-service.js.map +1 -1
  75. package/esm/services/screen-service.spec.js +19 -18
  76. package/esm/services/screen-service.spec.js.map +1 -1
  77. package/esm/services/spatial-navigation-service.d.ts +17 -39
  78. package/esm/services/spatial-navigation-service.d.ts.map +1 -1
  79. package/esm/services/spatial-navigation-service.js +280 -313
  80. package/esm/services/spatial-navigation-service.js.map +1 -1
  81. package/esm/services/spatial-navigation-service.spec.js +163 -158
  82. package/esm/services/spatial-navigation-service.spec.js.map +1 -1
  83. package/esm/shade.d.ts +1 -1
  84. package/esm/shade.d.ts.map +1 -1
  85. package/esm/shade.js +10 -7
  86. package/esm/shade.js.map +1 -1
  87. package/esm/vnode.d.ts +1 -1
  88. package/esm/vnode.js +1 -1
  89. package/package.json +5 -4
  90. package/src/component-factory.spec.tsx +3 -2
  91. package/src/components/define-nested-routes.spec.ts +91 -0
  92. package/src/components/define-nested-routes.ts +27 -0
  93. package/src/components/index.ts +3 -3
  94. package/src/components/nested-hooks.spec.ts +254 -0
  95. package/src/components/nested-hooks.ts +109 -0
  96. package/src/components/nested-navigate.spec.ts +136 -32
  97. package/src/components/nested-navigate.ts +75 -26
  98. package/src/components/nested-replace.spec.ts +89 -0
  99. package/src/components/nested-replace.ts +46 -0
  100. package/src/components/nested-route-link.spec.tsx +90 -22
  101. package/src/components/nested-route-link.tsx +54 -29
  102. package/src/components/nested-route-types.ts +82 -2
  103. package/src/components/nested-router.spec.tsx +334 -51
  104. package/src/components/nested-router.tsx +184 -24
  105. package/src/services/location-service.spec.ts +68 -25
  106. package/src/services/location-service.tsx +156 -122
  107. package/src/services/route-match-service.spec.ts +41 -28
  108. package/src/services/route-match-service.ts +17 -8
  109. package/src/services/route-meta-utils.spec.ts +78 -10
  110. package/src/services/route-meta-utils.ts +44 -15
  111. package/src/services/screen-service.spec.ts +19 -18
  112. package/src/services/screen-service.ts +65 -110
  113. package/src/services/spatial-navigation-service.spec.ts +165 -160
  114. package/src/services/spatial-navigation-service.ts +299 -287
  115. package/src/shade.ts +11 -8
  116. package/src/vnode.ts +1 -1
  117. package/esm/components/link-to-route.d.ts +0 -10
  118. package/esm/components/link-to-route.d.ts.map +0 -1
  119. package/esm/components/link-to-route.js +0 -16
  120. package/esm/components/link-to-route.js.map +0 -1
  121. package/esm/components/link-to-route.spec.d.ts +0 -2
  122. package/esm/components/link-to-route.spec.d.ts.map +0 -1
  123. package/esm/components/link-to-route.spec.js +0 -30
  124. package/esm/components/link-to-route.spec.js.map +0 -1
  125. package/esm/components/route-link.d.ts +0 -10
  126. package/esm/components/route-link.d.ts.map +0 -1
  127. package/esm/components/route-link.js +0 -25
  128. package/esm/components/route-link.js.map +0 -1
  129. package/esm/components/route-link.spec.d.ts +0 -2
  130. package/esm/components/route-link.spec.d.ts.map +0 -1
  131. package/esm/components/route-link.spec.js +0 -34
  132. package/esm/components/route-link.spec.js.map +0 -1
  133. package/esm/components/router.d.ts +0 -44
  134. package/esm/components/router.d.ts.map +0 -1
  135. package/esm/components/router.js +0 -66
  136. package/esm/components/router.js.map +0 -1
  137. package/esm/components/router.spec.d.ts +0 -2
  138. package/esm/components/router.spec.d.ts.map +0 -1
  139. package/esm/components/router.spec.js +0 -161
  140. package/esm/components/router.spec.js.map +0 -1
  141. package/src/components/link-to-route.spec.tsx +0 -45
  142. package/src/components/link-to-route.tsx +0 -26
  143. package/src/components/route-link.spec.tsx +0 -44
  144. package/src/components/route-link.tsx +0 -29
  145. package/src/components/router.spec.tsx +0 -217
  146. package/src/components/router.tsx +0 -103
package/CHANGELOG.md CHANGED
@@ -1,5 +1,162 @@
1
1
  # Changelog
2
2
 
3
+ ## [15.0.0] - 2026-04-25
4
+
5
+ ### 💥 Breaking Changes
6
+
7
+ All Shades services are now plain-object factories behind DI tokens. See the [v7 migration guide](../../docs/migrations/v7-functional-di.md) for rationale, recipes, and pitfalls.
8
+
9
+ - `LocationService`, `RouteMatchService`, `ScreenService`, and `SpatialNavigationService` are now interface + singleton token pairs. No class exports. Factory closures hold state and register `onDispose` teardown (event listeners, `history.pushState`/`replaceState` restoration, etc.).
10
+ - Added `LocationServiceSettings` and `SpatialNavigationSettings` singleton tokens with sensible defaults. `useCustomSearchStateSerializer(injector, serialize, deserialize)` and `configureSpatialNavigation(injector, options)` `bind` the settings and `invalidate` the dependent service. `useCustomSearchStateSerializer` preserves the v6 "must be called before first resolve" contract by throwing if `LocationService` has already been resolved (it patches `history.pushState`/`replaceState` and registers global listeners on construction, so a late rebind would leak the previous instance until injector disposal).
11
+ - `injector.cachedSingletons` is gone (it came from `@furystack/inject`). If you used `cachedSingletons.has(X)` to detect opt-in registration, replace with a nullable scoped token (`Token<T | null, 'scoped'>`, default `null`) bound by the parent component.
12
+ - `hasInjectorReference` is gone. `shade.ts` now inlines an `instanceof Injector` check on `props.injector`.
13
+ - `Constructable` import moved from `@furystack/inject` to `@furystack/core`. This package now has a direct `@furystack/core` dependency.
14
+ - `shade.ts` keeps its bare `new Injector()` fallback for components rendered outside an initialized DI context — this is documented and intentional. Apps should still go through `initializeShadeRoot`.
15
+
16
+ ## [14.0.0] - 2026-04-22
17
+
18
+ ### 💥 Breaking Changes
19
+
20
+ ### Removed the legacy flat router, `RouteLink`, and `LinkToRoute`
21
+
22
+ The long-deprecated single-level `Router` component, its supporting `Route` / `RouterProps` / `RouterState` types, and the legacy `RouteLink` / `LinkToRoute` navigation components have been removed. They were superseded by the nested router stack (`NestedRouter`, `NestedRouteLink`, `createNestedRouteLink`, `nestedNavigate`, etc.) and marked `@deprecated` for several releases.
23
+
24
+ **Removed exports:**
25
+
26
+ - `Router`, `RouterProps`, `RouterState`, `Route`
27
+ - `RouteLink`, `RouteLinkProps`
28
+ - `LinkToRoute`, `LinkToRouteProps`
29
+
30
+ **Impact:** Any module still importing `Router`, `RouteLink`, or `LinkToRoute` from `@furystack/shades` will fail to resolve.
31
+
32
+ **Migration:** Replace flat routers with `NestedRouter` + `defineNestedRoutes`, and swap `RouteLink` / `LinkToRoute` for `NestedRouteLink` (or the route-tree-aware `createNestedRouteLink<typeof routes>()` factory). See the `NestedRouter` section in the package README for the migration recipe.
33
+
34
+ ### `NestedRouteLink` and `nestedNavigate` now accept a `path` prop / object argument
35
+
36
+ `NestedRouteLink` now uses `path` instead of `href`, matching the route-definition vocabulary used by `Breadcrumb` and the new navigation helpers. `nestedNavigate` / `createNestedNavigate` switched from positional arguments to an options object so that the new `query` and `hash` fields can be passed alongside `path` / `params` without a growing parameter list.
37
+
38
+ **Before:**
39
+
40
+ ```typescript
41
+ <NestedRouteLink href="/users/:id" params={{ id: '1' }}>User</NestedRouteLink>
42
+
43
+ nestedNavigate(injector, '/users/:id', { id: '1' })
44
+ ```
45
+
46
+ **After:**
47
+
48
+ ```typescript
49
+ <NestedRouteLink path="/users/:id" params={{ id: '1' }}>User</NestedRouteLink>
50
+
51
+ nestedNavigate(injector, { path: '/users/:id', params: { id: '1' } })
52
+ ```
53
+
54
+ **Impact:** Every call site of `NestedRouteLink`, `nestedNavigate`, `createNestedRouteLink` and `createNestedNavigate` must be updated. Wrappers that forward `NestedRouteLinkProps` (for example `AppBarLink` in `@furystack/shades-common-components`) also switched to `path`.
55
+
56
+ **Migration:** Rename `href` → `path` on every `NestedRouteLink` call site, and rewrite `nestedNavigate(injector, path, params?)` as `nestedNavigate(injector, { path, params })`.
57
+
58
+ ### `NestedRoute` gained two optional generic parameters
59
+
60
+ `NestedRoute<TMatchResult>` is now `NestedRoute<TMatchResult, TQuery, THash>`. Existing single-generic usages keep working through the defaults, but the `component` opts are now always called with `query` and `hash` fields (populated with `null` / `undefined` when the route declares no schema). `MatchChainEntry` likewise carries `query: unknown` and `hash: string | undefined` fields.
61
+
62
+ **Impact:** Test fixtures that construct `MatchChainEntry` literals by hand must supply `query` and `hash`. Components that destructure `component` opts by name are unaffected.
63
+
64
+ **Migration:** Add `query: null, hash: undefined` to any hand-rolled `MatchChainEntry` literal (typically in tests).
65
+
66
+ ### ✨ Features
67
+
68
+ ### Route-level `hash` and `query` schemas
69
+
70
+ `NestedRoute` can now declare a readonly tuple of allowed hash literals and a query-string validator. Both flow through the new `createNestedRouteLink` / `createNestedNavigate` / `createNestedHooks` helpers to constrain call sites at compile time and surface typed values to the route's `component` opts at render time.
71
+
72
+ **Usage:**
73
+
74
+ ```typescript
75
+ const routes = defineNestedRoutes({
76
+ '/users/:id': {
77
+ hash: ['profile', 'settings'] as const,
78
+ query: (raw): { page: number } | null =>
79
+ typeof raw.page === 'number' ? { page: raw.page } : null,
80
+ component: ({ match, query, hash }) => <UserPage match={match} query={query} hash={hash} />,
81
+ },
82
+ })
83
+
84
+ const AppLink = createNestedRouteLink<typeof routes>()
85
+ const appNavigate = createNestedNavigate<typeof routes>()
86
+
87
+ <AppLink path="/users/:id" params={{ id: '1' }} query={{ page: 2 }} hash="profile">User</AppLink>
88
+ appNavigate(injector, { path: '/users/:id', params: { id: '1' }, hash: 'settings' })
89
+ ```
90
+
91
+ - The router matches on path only — an invalid query / hash never prevents navigation. Components receive `query: null` or `hash: undefined` when the URL does not satisfy the declared schema.
92
+ - Query serialization uses the existing `@furystack/rest` encoder, so any JSON-serializable shape round-trips without a schema library dependency.
93
+
94
+ ### `defineNestedRoutes` helper
95
+
96
+ `defineNestedRoutes(...)` is an identity helper that preserves the exact inferred literal type of a route tree while still applying a structural `Record<string, NestedRoute<...>>` constraint. Using a plain `satisfies` assertion with the historical bound `NestedRoute<any>` collapsed per-route generics to their defaults, which defeated the downstream extractors used by `createNestedRouteLink` / `createNestedNavigate` / `createNestedHooks`. Declaring route trees through `defineNestedRoutes` keeps the literal shape intact so each route's declared query validator return type and hash literal tuple remain recoverable.
97
+
98
+ ```typescript
99
+ export const appRoutes = defineNestedRoutes({
100
+ '/tabs': { hash: ['a', 'b'] as const, component: () => <div /> },
101
+ })
102
+ ```
103
+
104
+ ### `createNestedHooks` factory
105
+
106
+ `createNestedHooks(routes)` returns `{ getTypedQuery, getTypedHash }`, synchronous read helpers that look up the route declared at a given path and narrow the current URL's query / hash against its schema. The route tree is captured at factory time, so call sites only pass `(injector, path)` and the type-level narrowing is inferred from the tree. Intended for non-component consumers (services, side effects) that need a typed snapshot without subscribing through the router.
107
+
108
+ ```typescript
109
+ const { getTypedQuery, getTypedHash } = createNestedHooks(routes)
110
+ const query = getTypedQuery(injector, '/users/:id') // typed | null
111
+ const hash = getTypedHash(injector, '/users/:id') // typed | undefined
112
+ ```
113
+
114
+ ### `buildNestedNavigateUrl` utility
115
+
116
+ Exposed the URL-composition helper used internally by `nestedNavigate` and `NestedRouteLink` so callers can produce a compiled URL (path + query + hash) without triggering navigation.
117
+
118
+ ### Strongly-typed `extractNavTree` / `NavTreeNode<TRoutes>`
119
+
120
+ `extractNavTree` is now generic over the route tree it is called with, and returns `NavTreeNode<TRoutes>[]` instead of the previous loosely-typed `NavTreeNode[]`. On each node `pattern` is narrowed to `keyof TRoutes & string` and `fullPath` is narrowed to `ExtractRoutePaths<TRoutes> & string`, so the result feeds directly into `createNestedRouteLink` / `createNestedNavigate` / `createNestedReplace` / `createAppBarLink` without an `as` cast at the call site. Consumers that still reference `NavTreeNode` without a type argument keep compiling via a widened default parameter, preserving backward compatibility.
121
+
122
+ ```typescript
123
+ import { extractNavTree, type NavTreeNode } from '@furystack/shades'
124
+
125
+ const tree = extractNavTree(appRoutes)
126
+ // tree[0].fullPath is 'ExtractRoutePaths<typeof appRoutes>', not `string`.
127
+
128
+ const AppLink = createNestedRouteLink<typeof appRoutes>()
129
+ tree.map((node) => <AppLink path={node.fullPath}>{node.meta?.title}</AppLink>)
130
+
131
+ // Widened default is unchanged
132
+ const loose: NavTreeNode = { pattern: '/anything', fullPath: '/anything' }
133
+ ```
134
+
135
+ ### `nestedReplace` / `createNestedReplace`
136
+
137
+ Symmetric counterparts to `nestedNavigate` / `createNestedNavigate` that call `history.replaceState` (via `LocationService.replace`) instead of `pushState`. Use this for SPA redirects — auth guards, canonicalization — where the intermediate URL should not be recoverable with the browser's Back button. Accepts the exact same object-argument shape and inherits the full type-safe narrowing of `path` / `params` / `query` / `hash` against the route tree.
138
+
139
+ ```typescript
140
+ const appReplace = createNestedReplace<typeof appRoutes>()
141
+
142
+ // Unauthenticated visitor → send to /login without polluting history
143
+ appReplace(injector, { path: '/login' })
144
+ ```
145
+
146
+ ### ♻️ Refactoring
147
+
148
+ - The nested router now subscribes to query-string and hash changes in addition to path changes, re-rendering the matched chain without firing `onLeave` / `onVisit` lifecycle hooks when only the URL's search or hash segment changes.
149
+ - Path / query / hash observable bursts from a single `navigate` are now coalesced onto one microtask, so one logical navigation triggers one `updateUrl` run (the `versionRef` guard is retained as a race-safety net).
150
+ - `findDivergenceIndex` keeps its path-only semantics; a separate `hasQueryOrHashChanged` helper drives re-render decisions when the matched chain stays stable but its query / hash differ. Query values are compared with a key-order-independent shallow equality helper instead of `JSON.stringify`.
151
+ - `enrichMatchChain` short-circuits when no chain entry declares `query` or `hash`, returning the input array reference to avoid a per-navigation allocation on the common path-only case.
152
+ - New low-level exports surfaced for advanced routing integrations: `enrichMatchChain`, `hasQueryOrHashChanged`, and the supporting types `QueryValidator`, `HashLiterals`, `ExtractRouteQuery`, `ExtractRouteHash`, `RouteAt`, `TypedParamsArg`, `TypedQueryArg`, `TypedHashArg`, and `TreeAwareNestedRouteLinkProps`.
153
+
154
+ ### 🧪 Tests
155
+
156
+ - Added type-level and runtime coverage for the new `query` / `hash` paths on `createNestedNavigate`, `createNestedRouteLink`, and the router's match-chain enrichment.
157
+ - Added a `buildNestedNavigateUrl` suite covering params, query and hash composition.
158
+ - Added `expectTypeOf` assertions for the new generic `extractNavTree` output: `pattern` / `fullPath` narrow to the declared route keys and composed path union for flat and nested typed trees, and the widened `NavTreeNode` default keeps compiling for ad-hoc consumers.
159
+
3
160
  ## [13.2.2] - 2026-04-17
4
161
 
5
162
  ### ⬆️ Dependencies
@@ -1 +1 @@
1
- {"version":3,"file":"component-factory.spec.d.ts","sourceRoot":"","sources":["../src/component-factory.spec.tsx"],"names":[],"mappings":"AAEA,OAAO,UAAU,CAAA"}
1
+ {"version":3,"file":"component-factory.spec.d.ts","sourceRoot":"","sources":["../src/component-factory.spec.tsx"],"names":[],"mappings":"AAGA,OAAO,UAAU,CAAA"}
@@ -1,4 +1,4 @@
1
- import { Injector } from '@furystack/inject';
1
+ import { createInjector } from '@furystack/inject';
2
2
  import { describe, expect, it, vi } from 'vitest';
3
3
  import './jsx.js';
4
4
  import { createComponent } from './shade-component.js';
@@ -59,7 +59,7 @@ describe('Shades Component Factory', () => {
59
59
  render: ({ props }) => createComponent("div", null, props.foo),
60
60
  });
61
61
  const component = (createComponent("div", null,
62
- createComponent(Example, { foo: "example", injector: new Injector() })));
62
+ createComponent(Example, { foo: "example", injector: createInjector() })));
63
63
  const shade = component.firstElementChild;
64
64
  shade.updateComponent();
65
65
  await flushUpdates();
@@ -1 +1 @@
1
- {"version":3,"file":"component-factory.spec.js","sourceRoot":"","sources":["../src/component-factory.spec.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AACjD,OAAO,UAAU,CAAA;AACjB,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAEhD,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACxC,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;YAC1C,MAAM,SAAS,GAAG,oCAAe,CAAA;YACjC,MAAM,CAAC,SAAS,CAAC,CAAC,cAAc,CAAC,cAAc,CAAC,CAAA;YAChD,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YACxC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAA;QACrD,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;YAC7B,MAAM,SAAS,GAAG,yBAAK,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,QAAS,CAAA;YACvD,MAAM,CAAC,SAAS,CAAC,CAAC,cAAc,CAAC,cAAc,CAAC,CAAA;YAChD,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC3C,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;YACtC,MAAM,SAAS,GAAG,wCAAiB,SAAS,QAAQ,CAAA;YACpD,MAAM,CAAC,SAAS,CAAC,CAAC,cAAc,CAAC,cAAc,CAAC,CAAA;YAChD,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QAC/D,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;YACtC,MAAM,SAAS,GAAG,CAChB,uCAAgB,UAAU,iBAAa,MAAM,QAEvC,CACP,CAAA;YACD,MAAM,CAAC,SAAS,CAAC,CAAC,cAAc,CAAC,cAAc,CAAC,CAAA;YAChD,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;YAC7D,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAC5D,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;YAC1C,MAAM,SAAS,GAAG,CAChB,yBAAK,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE;gBAC7B,+CAAyB;gBACzB;;oBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAK;gBACrC,uBAAG,MAAM,EAAC,QAAQ,EAAC,IAAI,EAAC,oBAAoB,WAExC,CACA,CACP,CAAA;YACD,MAAM,CAAC,SAAS,CAAC,CAAC,cAAc,CAAC,cAAc,CAAC,CAAA;YAChD,MAAM,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YAC3C,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,IAAI,CAC9B,uGAAuG,CACxG,CAAA;QACH,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,yBAAyB,EAAE,GAAG,EAAE;YACjC,MAAM,OAAO,GAAG,EAAE,CAAC,EAAE,EAAE,CAAA;YACvB,MAAM,SAAS,GAAG,yBAAK,OAAO,EAAE,OAAO,GAAQ,CAAA;YAC/C,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACzC,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAChC,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;YACzC,MAAM,OAAO,GAAG,KAAK,CAAC,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,4BAAO,EAAE,CAAC,CAAA;YAEpF,MAAM,SAAS,GAAG,CAChB;gBACE,gBAAC,OAAO,OAAG,CACP,CACP,CAAA;YAED,MAAM,KAAK,GAAG,SAAS,CAAC,iBAAgC,CAAA;YACxD,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;YAC/B,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QACzC,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;YACpD,MAAM,OAAO,GAAG,KAAK,CAAsC;gBACzD,iBAAiB,EAAE,oBAAoB;gBACvC,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,6BAAM,KAAK,CAAC,GAAG,CAAO;aAC9C,CAAC,CAAA;YAEF,MAAM,SAAS,GAAG,CAChB;gBACE,gBAAC,OAAO,IAAC,GAAG,EAAC,SAAS,EAAC,QAAQ,EAAE,IAAI,QAAQ,EAAE,GAAI,CAC/C,CACP,CAAA;YAED,MAAM,KAAK,GAAG,SAAS,CAAC,iBAAiD,CAAA;YAEzE,KAAK,CAAC,eAAe,EAAE,CAAA;YACvB,MAAM,YAAY,EAAE,CAAA;YAEpB,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;YAC1C,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;YAEvC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAA;QACpD,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;YACpD,MAAM,OAAO,GAAG,KAAK,CAAC;gBACpB,iBAAiB,EAAE,oBAAoB;gBACvC,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE;oBACvB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;oBAC1B,OAAO,4BAAO,CAAA;gBAChB,CAAC;aACF,CAAC,CAAA;YAEF,MAAM,SAAS,GAAG,CAChB;gBACE,gBAAC,OAAO,OAAG,CACP,CACP,CAAA;YAED,MAAM,KAAK,GAAG,SAAS,CAAC,iBAAgC,CAAA;YACxD,KAAK,CAAC,eAAe,EAAE,CAAA;YACvB,MAAM,YAAY,EAAE,CAAA;YACpB,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;YACtF,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QACzC,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"component-factory.spec.js","sourceRoot":"","sources":["../src/component-factory.spec.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { NestedRoute } from './nested-router.js';
2
+ /**
3
+ * Identity helper that preserves the exact inferred literal type of a nested
4
+ * route tree while still applying a structural `Record<string, NestedRoute<...>>`
5
+ * constraint for compile-time validation.
6
+ *
7
+ * Using a plain `satisfies Record<string, NestedRoute<any>>` assertion collapses
8
+ * the per-route `query` / `hash` generics to their defaults, which defeats the
9
+ * type-safe navigation helpers (`createNestedRouteLink`, `createNestedNavigate`,
10
+ * `createNestedHooks`). Declaring route trees through this helper keeps the
11
+ * literal shape intact so downstream extractors can recover each route's
12
+ * declared query validator return type and hash literal tuple.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * const routes = defineNestedRoutes({
17
+ * '/users/:id': {
18
+ * component: ({ match, query, hash }) => ...,
19
+ * hash: ['profile', 'settings'] as const,
20
+ * query: (raw): { page: number } | null =>
21
+ * typeof raw.page === 'number' ? { page: raw.page } : null,
22
+ * },
23
+ * })
24
+ * ```
25
+ */
26
+ export declare const defineNestedRoutes: <T extends Record<string, NestedRoute<any, any, any>>>(routes: T) => T;
27
+ //# sourceMappingURL=define-nested-routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-nested-routes.d.ts","sourceRoot":"","sources":["../../src/components/define-nested-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAErD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,kBAAkB,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAG,CAAW,CAAA"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Identity helper that preserves the exact inferred literal type of a nested
3
+ * route tree while still applying a structural `Record<string, NestedRoute<...>>`
4
+ * constraint for compile-time validation.
5
+ *
6
+ * Using a plain `satisfies Record<string, NestedRoute<any>>` assertion collapses
7
+ * the per-route `query` / `hash` generics to their defaults, which defeats the
8
+ * type-safe navigation helpers (`createNestedRouteLink`, `createNestedNavigate`,
9
+ * `createNestedHooks`). Declaring route trees through this helper keeps the
10
+ * literal shape intact so downstream extractors can recover each route's
11
+ * declared query validator return type and hash literal tuple.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const routes = defineNestedRoutes({
16
+ * '/users/:id': {
17
+ * component: ({ match, query, hash }) => ...,
18
+ * hash: ['profile', 'settings'] as const,
19
+ * query: (raw): { page: number } | null =>
20
+ * typeof raw.page === 'number' ? { page: raw.page } : null,
21
+ * },
22
+ * })
23
+ * ```
24
+ */
25
+ export const defineNestedRoutes = (routes) => routes;
26
+ //# sourceMappingURL=define-nested-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-nested-routes.js","sourceRoot":"","sources":["../../src/components/define-nested-routes.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAuD,MAAS,EAAK,EAAE,CAAC,MAAM,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=define-nested-routes.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-nested-routes.spec.d.ts","sourceRoot":"","sources":["../../src/components/define-nested-routes.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,81 @@
1
+ import { describe, expect, expectTypeOf, it } from 'vitest';
2
+ import { defineNestedRoutes } from './define-nested-routes.js';
3
+ const makeComponent = () => null;
4
+ describe('defineNestedRoutes', () => {
5
+ it('Should return the same object reference at runtime', () => {
6
+ const input = {
7
+ '/tabs': {
8
+ component: () => makeComponent(),
9
+ hash: ['a', 'b'],
10
+ },
11
+ };
12
+ const result = defineNestedRoutes(input);
13
+ expect(result).toBe(input);
14
+ });
15
+ it('Should accept values that satisfy Record<string, NestedRoute<...>>', () => {
16
+ const routes = defineNestedRoutes({
17
+ '/bare': { component: () => makeComponent() },
18
+ '/tabs': {
19
+ component: () => makeComponent(),
20
+ hash: ['a', 'b'],
21
+ query: (raw) => (typeof raw.page === 'number' ? { page: raw.page } : null),
22
+ },
23
+ });
24
+ expect(Object.keys(routes)).toEqual(['/bare', '/tabs']);
25
+ });
26
+ describe('Type preservation', () => {
27
+ it('Should preserve `hash` as a readonly tuple of literal values', () => {
28
+ const routes = defineNestedRoutes({
29
+ '/tabs': {
30
+ component: () => makeComponent(),
31
+ hash: ['overview', 'details'],
32
+ },
33
+ });
34
+ expectTypeOf(routes['/tabs'].hash).toEqualTypeOf();
35
+ });
36
+ it('Should preserve the declared return type of a `query` validator', () => {
37
+ const routes = defineNestedRoutes({
38
+ '/list': {
39
+ component: () => makeComponent(),
40
+ query: (raw) => (typeof raw.page === 'number' ? { page: raw.page } : null),
41
+ },
42
+ });
43
+ expectTypeOf().toEqualTypeOf();
44
+ });
45
+ it('Should preserve nested children literals', () => {
46
+ const routes = defineNestedRoutes({
47
+ '/navigation': {
48
+ component: () => makeComponent(),
49
+ children: {
50
+ '/tabs': {
51
+ component: () => makeComponent(),
52
+ hash: ['a'],
53
+ },
54
+ },
55
+ },
56
+ });
57
+ expectTypeOf().toEqualTypeOf();
58
+ });
59
+ it('Should reject values that do not satisfy the NestedRoute constraint', () => {
60
+ // @ts-expect-error -- `component` is required on every route entry
61
+ defineNestedRoutes({ '/invalid': { hash: ['a'] } });
62
+ });
63
+ });
64
+ describe('Satisfies-collapse regression coverage', () => {
65
+ it('Should keep literal shape intact (unlike a bare `satisfies` assertion)', () => {
66
+ // A plain `satisfies` on `Record<string, NestedRoute<any>>` would widen
67
+ // the per-route `hash` to `readonly any[]` and drop the literal tuple.
68
+ // `defineNestedRoutes` must preserve the narrow tuple so downstream
69
+ // type extractors (`ExtractRouteHash`, `RouteAt`) can recover it.
70
+ const routes = defineNestedRoutes({
71
+ '/tabs': {
72
+ component: () => makeComponent(),
73
+ hash: ['overview'],
74
+ },
75
+ });
76
+ expectTypeOf().not.toEqualTypeOf();
77
+ expectTypeOf().toEqualTypeOf();
78
+ });
79
+ });
80
+ });
81
+ //# sourceMappingURL=define-nested-routes.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-nested-routes.spec.js","sourceRoot":"","sources":["../../src/components/define-nested-routes.spec.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,9 @@
1
+ export * from './define-nested-routes.js';
1
2
  export * from './lazy-load.js';
2
- export * from './link-to-route.js';
3
+ export * from './nested-hooks.js';
3
4
  export * from './nested-navigate.js';
5
+ export * from './nested-replace.js';
4
6
  export * from './nested-route-link.js';
5
7
  export * from './nested-route-types.js';
6
8
  export * from './nested-router.js';
7
- export * from './route-link.js';
8
- export * from './router.js';
9
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,oBAAoB,CAAA;AAClC,cAAc,sBAAsB,CAAA;AACpC,cAAc,wBAAwB,CAAA;AACtC,cAAc,yBAAyB,CAAA;AACvC,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAA;AACzC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,sBAAsB,CAAA;AACpC,cAAc,qBAAqB,CAAA;AACnC,cAAc,wBAAwB,CAAA;AACtC,cAAc,yBAAyB,CAAA;AACvC,cAAc,oBAAoB,CAAA"}
@@ -1,9 +1,9 @@
1
+ export * from './define-nested-routes.js';
1
2
  export * from './lazy-load.js';
2
- export * from './link-to-route.js';
3
+ export * from './nested-hooks.js';
3
4
  export * from './nested-navigate.js';
5
+ export * from './nested-replace.js';
4
6
  export * from './nested-route-link.js';
5
7
  export * from './nested-route-types.js';
6
8
  export * from './nested-router.js';
7
- export * from './route-link.js';
8
- export * from './router.js';
9
9
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,oBAAoB,CAAA;AAClC,cAAc,sBAAsB,CAAA;AACpC,cAAc,wBAAwB,CAAA;AACtC,cAAc,yBAAyB,CAAA;AACvC,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAA;AACzC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,sBAAsB,CAAA;AACpC,cAAc,qBAAqB,CAAA;AACnC,cAAc,wBAAwB,CAAA;AACtC,cAAc,yBAAyB,CAAA;AACvC,cAAc,oBAAoB,CAAA"}
@@ -0,0 +1,62 @@
1
+ import type { Injector } from '@furystack/inject';
2
+ import type { ExtractRouteHash, ExtractRouteQuery, ExtractRoutePaths, RouteAt } from './nested-route-types.js';
3
+ import type { NestedRoute } from './nested-router.js';
4
+ /**
5
+ * A pair of synchronous read helpers bound to a specific route tree. Use the
6
+ * factory {@link createNestedHooks} to produce one.
7
+ */
8
+ export type NestedHooks<TRoutes extends Record<string, NestedRoute<any, any, any>>> = {
9
+ /**
10
+ * Reads and validates the current URL query string against the declared
11
+ * validator of the route at `path`. Returns the typed query value, or
12
+ * `null` when the route has no validator or the current URL's query does
13
+ * not satisfy it.
14
+ *
15
+ * This is a synchronous snapshot — for reactive use, subscribe to
16
+ * `locationService.onDeserializedLocationSearchChanged` and call this read
17
+ * on each change.
18
+ */
19
+ getTypedQuery: <TPath extends ExtractRoutePaths<TRoutes>>(injector: Injector, path: TPath) => ExtractRouteQuery<RouteAt<TRoutes, TPath>> | null;
20
+ /**
21
+ * Reads the current URL hash and narrows it against the declared literal
22
+ * tuple of the route at `path`. Returns the hash when it matches one of
23
+ * the route's declared literals, or `undefined` otherwise.
24
+ *
25
+ * This is a synchronous snapshot — for reactive use, subscribe to
26
+ * `locationService.onLocationHashChanged` and call this read on each change.
27
+ */
28
+ getTypedHash: <TPath extends ExtractRoutePaths<TRoutes>>(injector: Injector, path: TPath) => ExtractRouteHash<RouteAt<TRoutes, TPath>> | undefined;
29
+ };
30
+ /**
31
+ * Walks a route tree and returns the route value declared at the given path,
32
+ * matching each URL pattern segment-by-segment. The root `/` pattern is
33
+ * transparent: its children are searched with the full path.
34
+ *
35
+ * Exported for unit testing only — consumers should go through
36
+ * {@link createNestedHooks}.
37
+ *
38
+ * @internal
39
+ */
40
+ export declare const walkRoute: (routes: Record<string, NestedRoute<any, any, any>>, path: string) => NestedRoute<any, any, any> | undefined;
41
+ /**
42
+ * Creates a pair of type-safe synchronous read helpers (`getTypedQuery`,
43
+ * `getTypedHash`) bound to a specific route tree. The returned functions
44
+ * validate the current URL's query and hash against the route declared at
45
+ * the given path, returning typed values or `null`/`undefined` on mismatch.
46
+ *
47
+ * The concrete route tree is captured at factory time, so call sites only
48
+ * pass `(injector, path)`; the type-level narrowing is derived from the
49
+ * inferred tree generic.
50
+ *
51
+ * @typeParam TRoutes - The route tree type (inferred from the `routes` argument)
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * const { getTypedQuery, getTypedHash } = createNestedHooks(appRoutes)
56
+ *
57
+ * const query = getTypedQuery(injector, '/users') // typed
58
+ * const hash = getTypedHash(injector, '/users') // typed
59
+ * ```
60
+ */
61
+ export declare const createNestedHooks: <TRoutes extends Record<string, NestedRoute<any, any, any>>>(routes: TRoutes) => NestedHooks<TRoutes>;
62
+ //# sourceMappingURL=nested-hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nested-hooks.d.ts","sourceRoot":"","sources":["../../src/components/nested-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAEjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAA;AAC9G,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAErD;;;GAGG;AACH,MAAM,MAAM,WAAW,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,IAAI;IACpF;;;;;;;;;OASG;IACH,aAAa,EAAE,CAAC,KAAK,SAAS,iBAAiB,CAAC,OAAO,CAAC,EACtD,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,KAAK,KACR,iBAAiB,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,GAAG,IAAI,CAAA;IACtD;;;;;;;OAOG;IACH,YAAY,EAAE,CAAC,KAAK,SAAS,iBAAiB,CAAC,OAAO,CAAC,EACrD,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,KAAK,KACR,gBAAgB,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,GAAG,SAAS,CAAA;CAC3D,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,SAAS,GACpB,QAAQ,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,EAClD,MAAM,MAAM,KACX,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,SAe/B,CAAA;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,EAC1F,QAAQ,OAAO,KACd,WAAW,CAAC,OAAO,CAkBrB,CAAA"}
@@ -0,0 +1,73 @@
1
+ import { LocationService } from '../services/location-service.js';
2
+ /**
3
+ * Walks a route tree and returns the route value declared at the given path,
4
+ * matching each URL pattern segment-by-segment. The root `/` pattern is
5
+ * transparent: its children are searched with the full path.
6
+ *
7
+ * Exported for unit testing only — consumers should go through
8
+ * {@link createNestedHooks}.
9
+ *
10
+ * @internal
11
+ */
12
+ export const walkRoute = (routes, path) => {
13
+ for (const [pattern, route] of Object.entries(routes)) {
14
+ if (pattern === path)
15
+ return route;
16
+ if (pattern === '/' && route.children) {
17
+ const nested = walkRoute(route.children, path);
18
+ if (nested)
19
+ return nested;
20
+ }
21
+ else if (route.children && path.startsWith(pattern)) {
22
+ const rest = path.slice(pattern.length);
23
+ if (rest.startsWith('/') || rest === '') {
24
+ const nested = walkRoute(route.children, rest || '/');
25
+ if (nested)
26
+ return nested;
27
+ }
28
+ }
29
+ }
30
+ return undefined;
31
+ };
32
+ /**
33
+ * Creates a pair of type-safe synchronous read helpers (`getTypedQuery`,
34
+ * `getTypedHash`) bound to a specific route tree. The returned functions
35
+ * validate the current URL's query and hash against the route declared at
36
+ * the given path, returning typed values or `null`/`undefined` on mismatch.
37
+ *
38
+ * The concrete route tree is captured at factory time, so call sites only
39
+ * pass `(injector, path)`; the type-level narrowing is derived from the
40
+ * inferred tree generic.
41
+ *
42
+ * @typeParam TRoutes - The route tree type (inferred from the `routes` argument)
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * const { getTypedQuery, getTypedHash } = createNestedHooks(appRoutes)
47
+ *
48
+ * const query = getTypedQuery(injector, '/users') // typed
49
+ * const hash = getTypedHash(injector, '/users') // typed
50
+ * ```
51
+ */
52
+ export const createNestedHooks = (routes) => {
53
+ return {
54
+ getTypedQuery: (injector, path) => {
55
+ const route = walkRoute(routes, path);
56
+ if (!route?.query)
57
+ return null;
58
+ const locationService = injector.get(LocationService);
59
+ const deserialized = locationService.onDeserializedLocationSearchChanged.getValue();
60
+ return route.query(deserialized);
61
+ },
62
+ getTypedHash: (injector, path) => {
63
+ const route = walkRoute(routes, path);
64
+ const declaredHash = route?.hash;
65
+ if (!declaredHash)
66
+ return undefined;
67
+ const locationService = injector.get(LocationService);
68
+ const currentHash = locationService.onLocationHashChanged.getValue();
69
+ return declaredHash.includes(currentHash) ? currentHash : undefined;
70
+ },
71
+ };
72
+ };
73
+ //# sourceMappingURL=nested-hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nested-hooks.js","sourceRoot":"","sources":["../../src/components/nested-hooks.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAA;AAqCjE;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,MAAkD,EAClD,IAAY,EAC4B,EAAE;IAC1C,KAAK,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACtD,IAAI,OAAO,KAAK,IAAI;YAAE,OAAO,KAAK,CAAA;QAClC,IAAI,OAAO,KAAK,GAAG,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACtC,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;YAC9C,IAAI,MAAM;gBAAE,OAAO,MAAM,CAAA;QAC3B,CAAC;aAAM,IAAI,KAAK,CAAC,QAAQ,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACtD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;YACvC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;gBACxC,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,IAAI,GAAG,CAAC,CAAA;gBACrD,IAAI,MAAM;oBAAE,OAAO,MAAM,CAAA;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC,CAAA;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,MAAe,EACO,EAAE;IACxB,OAAO;QACL,aAAa,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE;YAChC,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;YACrC,IAAI,CAAC,KAAK,EAAE,KAAK;gBAAE,OAAO,IAAI,CAAA;YAC9B,MAAM,eAAe,GAAG,QAAQ,CAAC,GAAG,CAAC,eAAe,CAAC,CAAA;YACrD,MAAM,YAAY,GAAG,eAAe,CAAC,mCAAmC,CAAC,QAAQ,EAAE,CAAA;YACnF,OAAO,KAAK,CAAC,KAAK,CAAC,YAAY,CAAU,CAAA;QAC3C,CAAC;QACD,YAAY,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE;YAC/B,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;YACrC,MAAM,YAAY,GAAG,KAAK,EAAE,IAAqC,CAAA;YACjE,IAAI,CAAC,YAAY;gBAAE,OAAO,SAAS,CAAA;YACnC,MAAM,eAAe,GAAG,QAAQ,CAAC,GAAG,CAAC,eAAe,CAAC,CAAA;YACrD,MAAM,WAAW,GAAG,eAAe,CAAC,qBAAqB,CAAC,QAAQ,EAAE,CAAA;YACpE,OAAO,YAAY,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAE,WAAqB,CAAC,CAAC,CAAC,SAAS,CAAA;QAChF,CAAC;KACF,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=nested-hooks.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nested-hooks.spec.d.ts","sourceRoot":"","sources":["../../src/components/nested-hooks.spec.ts"],"names":[],"mappings":""}