@pithyjs/router 0.1.0-beta.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 (163) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +621 -0
  3. package/dist/index.d.ts +29 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +49 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/router/core/config.d.ts +22 -0
  8. package/dist/router/core/config.d.ts.map +1 -0
  9. package/dist/router/core/config.js +54 -0
  10. package/dist/router/core/config.js.map +1 -0
  11. package/dist/router/core/constants.d.ts +89 -0
  12. package/dist/router/core/constants.d.ts.map +1 -0
  13. package/dist/router/core/constants.js +95 -0
  14. package/dist/router/core/constants.js.map +1 -0
  15. package/dist/router/core/history.d.ts +54 -0
  16. package/dist/router/core/history.d.ts.map +1 -0
  17. package/dist/router/core/history.js +89 -0
  18. package/dist/router/core/history.js.map +1 -0
  19. package/dist/router/core/matcher.d.ts +43 -0
  20. package/dist/router/core/matcher.d.ts.map +1 -0
  21. package/dist/router/core/matcher.js +242 -0
  22. package/dist/router/core/matcher.js.map +1 -0
  23. package/dist/router/core/params.d.ts +88 -0
  24. package/dist/router/core/params.d.ts.map +1 -0
  25. package/dist/router/core/params.js +150 -0
  26. package/dist/router/core/params.js.map +1 -0
  27. package/dist/router/core/pipeline.d.ts +90 -0
  28. package/dist/router/core/pipeline.d.ts.map +1 -0
  29. package/dist/router/core/pipeline.js +323 -0
  30. package/dist/router/core/pipeline.js.map +1 -0
  31. package/dist/router/core/route-tree.d.ts +76 -0
  32. package/dist/router/core/route-tree.d.ts.map +1 -0
  33. package/dist/router/core/route-tree.js +143 -0
  34. package/dist/router/core/route-tree.js.map +1 -0
  35. package/dist/router/core/router.d.ts +229 -0
  36. package/dist/router/core/router.d.ts.map +1 -0
  37. package/dist/router/core/router.js +762 -0
  38. package/dist/router/core/router.js.map +1 -0
  39. package/dist/router/core/types.d.ts +387 -0
  40. package/dist/router/core/types.d.ts.map +1 -0
  41. package/dist/router/core/types.js +22 -0
  42. package/dist/router/core/types.js.map +1 -0
  43. package/dist/router/core/utils.d.ts +66 -0
  44. package/dist/router/core/utils.d.ts.map +1 -0
  45. package/dist/router/core/utils.js +152 -0
  46. package/dist/router/core/utils.js.map +1 -0
  47. package/dist/router/features/coordinator.d.ts +75 -0
  48. package/dist/router/features/coordinator.d.ts.map +1 -0
  49. package/dist/router/features/coordinator.js +203 -0
  50. package/dist/router/features/coordinator.js.map +1 -0
  51. package/dist/router/features/errors/error-codes.d.ts +107 -0
  52. package/dist/router/features/errors/error-codes.d.ts.map +1 -0
  53. package/dist/router/features/errors/error-codes.js +247 -0
  54. package/dist/router/features/errors/error-codes.js.map +1 -0
  55. package/dist/router/features/errors/error-resolution.d.ts +31 -0
  56. package/dist/router/features/errors/error-resolution.d.ts.map +1 -0
  57. package/dist/router/features/errors/error-resolution.js +47 -0
  58. package/dist/router/features/errors/error-resolution.js.map +1 -0
  59. package/dist/router/features/errors/error-types.d.ts +60 -0
  60. package/dist/router/features/errors/error-types.d.ts.map +1 -0
  61. package/dist/router/features/errors/error-types.js +57 -0
  62. package/dist/router/features/errors/error-types.js.map +1 -0
  63. package/dist/router/features/errors/global-error-fallback.d.ts +33 -0
  64. package/dist/router/features/errors/global-error-fallback.d.ts.map +1 -0
  65. package/dist/router/features/errors/global-error-fallback.js +182 -0
  66. package/dist/router/features/errors/global-error-fallback.js.map +1 -0
  67. package/dist/router/features/errors/index.d.ts +15 -0
  68. package/dist/router/features/errors/index.d.ts.map +1 -0
  69. package/dist/router/features/errors/index.js +18 -0
  70. package/dist/router/features/errors/index.js.map +1 -0
  71. package/dist/router/features/errors/router-errors.d.ts +44 -0
  72. package/dist/router/features/errors/router-errors.d.ts.map +1 -0
  73. package/dist/router/features/errors/router-errors.js +53 -0
  74. package/dist/router/features/errors/router-errors.js.map +1 -0
  75. package/dist/router/features/guards/engine.d.ts +40 -0
  76. package/dist/router/features/guards/engine.d.ts.map +1 -0
  77. package/dist/router/features/guards/engine.js +67 -0
  78. package/dist/router/features/guards/engine.js.map +1 -0
  79. package/dist/router/features/guards/index.d.ts +52 -0
  80. package/dist/router/features/guards/index.d.ts.map +1 -0
  81. package/dist/router/features/guards/index.js +95 -0
  82. package/dist/router/features/guards/index.js.map +1 -0
  83. package/dist/router/features/loaders/cache.d.ts +60 -0
  84. package/dist/router/features/loaders/cache.d.ts.map +1 -0
  85. package/dist/router/features/loaders/cache.js +80 -0
  86. package/dist/router/features/loaders/cache.js.map +1 -0
  87. package/dist/router/features/loaders/engine.d.ts +17 -0
  88. package/dist/router/features/loaders/engine.d.ts.map +1 -0
  89. package/dist/router/features/loaders/engine.js +27 -0
  90. package/dist/router/features/loaders/engine.js.map +1 -0
  91. package/dist/router/features/loaders/index.d.ts +49 -0
  92. package/dist/router/features/loaders/index.d.ts.map +1 -0
  93. package/dist/router/features/loaders/index.js +112 -0
  94. package/dist/router/features/loaders/index.js.map +1 -0
  95. package/dist/router/features/loading-ui-manager.d.ts +69 -0
  96. package/dist/router/features/loading-ui-manager.d.ts.map +1 -0
  97. package/dist/router/features/loading-ui-manager.js +209 -0
  98. package/dist/router/features/loading-ui-manager.js.map +1 -0
  99. package/dist/router/features/module-loader.d.ts +50 -0
  100. package/dist/router/features/module-loader.d.ts.map +1 -0
  101. package/dist/router/features/module-loader.js +121 -0
  102. package/dist/router/features/module-loader.js.map +1 -0
  103. package/dist/router/features/outlet/errors.d.ts +24 -0
  104. package/dist/router/features/outlet/errors.d.ts.map +1 -0
  105. package/dist/router/features/outlet/errors.js +96 -0
  106. package/dist/router/features/outlet/errors.js.map +1 -0
  107. package/dist/router/features/outlet/index.d.ts +45 -0
  108. package/dist/router/features/outlet/index.d.ts.map +1 -0
  109. package/dist/router/features/outlet/index.js +95 -0
  110. package/dist/router/features/outlet/index.js.map +1 -0
  111. package/dist/router/features/outlet/mount.d.ts +38 -0
  112. package/dist/router/features/outlet/mount.d.ts.map +1 -0
  113. package/dist/router/features/outlet/mount.js +301 -0
  114. package/dist/router/features/outlet/mount.js.map +1 -0
  115. package/dist/router/features/outlet/segment-chain.d.ts +16 -0
  116. package/dist/router/features/outlet/segment-chain.d.ts.map +1 -0
  117. package/dist/router/features/outlet/segment-chain.js +62 -0
  118. package/dist/router/features/outlet/segment-chain.js.map +1 -0
  119. package/dist/router/features/prefetch/index.d.ts +97 -0
  120. package/dist/router/features/prefetch/index.d.ts.map +1 -0
  121. package/dist/router/features/prefetch/index.js +345 -0
  122. package/dist/router/features/prefetch/index.js.map +1 -0
  123. package/dist/router/features/ux/focus.d.ts +62 -0
  124. package/dist/router/features/ux/focus.d.ts.map +1 -0
  125. package/dist/router/features/ux/focus.js +136 -0
  126. package/dist/router/features/ux/focus.js.map +1 -0
  127. package/dist/router/features/ux/live-region.d.ts +81 -0
  128. package/dist/router/features/ux/live-region.d.ts.map +1 -0
  129. package/dist/router/features/ux/live-region.js +166 -0
  130. package/dist/router/features/ux/live-region.js.map +1 -0
  131. package/dist/router/features/ux/scroll-focus.d.ts +105 -0
  132. package/dist/router/features/ux/scroll-focus.d.ts.map +1 -0
  133. package/dist/router/features/ux/scroll-focus.js +111 -0
  134. package/dist/router/features/ux/scroll-focus.js.map +1 -0
  135. package/dist/router/features/ux/scroll.d.ts +71 -0
  136. package/dist/router/features/ux/scroll.d.ts.map +1 -0
  137. package/dist/router/features/ux/scroll.js +137 -0
  138. package/dist/router/features/ux/scroll.js.map +1 -0
  139. package/dist/router/features/ux/transitions.d.ts +120 -0
  140. package/dist/router/features/ux/transitions.d.ts.map +1 -0
  141. package/dist/router/features/ux/transitions.js +235 -0
  142. package/dist/router/features/ux/transitions.js.map +1 -0
  143. package/dist/router/integration/directive-binder.d.ts +20 -0
  144. package/dist/router/integration/directive-binder.d.ts.map +1 -0
  145. package/dist/router/integration/directive-binder.js +139 -0
  146. package/dist/router/integration/directive-binder.js.map +1 -0
  147. package/dist/router/integration/directives.d.ts +42 -0
  148. package/dist/router/integration/directives.d.ts.map +1 -0
  149. package/dist/router/integration/directives.js +109 -0
  150. package/dist/router/integration/directives.js.map +1 -0
  151. package/dist/router/integration/history-adapter.d.ts +76 -0
  152. package/dist/router/integration/history-adapter.d.ts.map +1 -0
  153. package/dist/router/integration/history-adapter.js +157 -0
  154. package/dist/router/integration/history-adapter.js.map +1 -0
  155. package/dist/router/integration/hmr.d.ts +83 -0
  156. package/dist/router/integration/hmr.d.ts.map +1 -0
  157. package/dist/router/integration/hmr.js +160 -0
  158. package/dist/router/integration/hmr.js.map +1 -0
  159. package/dist/router/integration/link-interceptor.d.ts +49 -0
  160. package/dist/router/integration/link-interceptor.d.ts.map +1 -0
  161. package/dist/router/integration/link-interceptor.js +94 -0
  162. package/dist/router/integration/link-interceptor.js.map +1 -0
  163. package/package.json +47 -0
@@ -0,0 +1,345 @@
1
+ /** @codex
2
+ {
3
+ "id": "pithy.router.prefetch",
4
+ "title": "Enhanced Prefetch System with Intelligent Heuristics",
5
+ "category": "runtime"
6
+ }
7
+ */
8
+ import { PREFETCH_DEFAULTS } from '../../core/constants';
9
+ import { DEFAULT_VISIBILITY_MARGIN, observeVisibilityOnce, } from '@pithyjs/shared';
10
+ /** @codexApi {"parent":"pithy.router.prefetch","name":"PREFETCH_STRATEGIES","stability":"stable","signature":"readonly PrefetchStrategy[]","description":"Runtime list of valid PrefetchStrategy values for enum validation"} */
11
+ export const PREFETCH_STRATEGIES = [
12
+ 'none',
13
+ 'hover',
14
+ 'visible',
15
+ 'intent',
16
+ ];
17
+ /** @codexApi {"parent":"pithy.router.prefetch","name":"PrefetchManager","stability":"stable","signature":"class PrefetchManager","description":"Intelligent resource prefetching with enhanced heuristics"} */
18
+ /**
19
+ * Prefetch manager for intelligent resource prefetching
20
+ * Implements enhanced prefetch policy & cache layer from architectural improvements
21
+ */
22
+ export class PrefetchManager {
23
+ constructor(config = {}) {
24
+ this.activeRequests = new Map();
25
+ this.hoverTimers = new Map();
26
+ this.intentTracking = new Map();
27
+ /** Track when each route was last prefetched with its TTL to avoid repeated prefetches */
28
+ this.prefetchedAt = new Map();
29
+ this.config = {
30
+ hoverDelay: config.hoverDelay ?? PREFETCH_DEFAULTS.HOVER_DELAY,
31
+ visibleMargin: config.visibleMargin ?? DEFAULT_VISIBILITY_MARGIN,
32
+ cacheTTL: config.cacheTTL ?? PREFETCH_DEFAULTS.CACHE_TTL,
33
+ intentVelocityThreshold: config.intentVelocityThreshold ??
34
+ PREFETCH_DEFAULTS.INTENT_VELOCITY_THRESHOLD,
35
+ intentDirectionSamples: config.intentDirectionSamples ??
36
+ PREFETCH_DEFAULTS.INTENT_DIRECTION_SAMPLES,
37
+ };
38
+ }
39
+ /** @codexApi {"parent":"pithy.router.prefetch","name":"registerLink","stability":"stable","signature":"(element: HTMLElement, strategy: PrefetchStrategy) => void","description":"Register a link element for prefetching with a given strategy"} */
40
+ /**
41
+ * Register a link for prefetching with specified strategy
42
+ */
43
+ registerLink(element, strategy) {
44
+ const href = element.getAttribute('href');
45
+ if (!href || strategy === 'none') {
46
+ return;
47
+ }
48
+ switch (strategy) {
49
+ case 'hover':
50
+ this.setupHoverPrefetch(element, href);
51
+ break;
52
+ case 'visible':
53
+ this.setupVisiblePrefetch(element);
54
+ break;
55
+ case 'intent':
56
+ this.setupIntentPrefetch(element, href);
57
+ break;
58
+ }
59
+ }
60
+ /** @codexApi {"parent":"pithy.router.prefetch","name":"unregisterLink","stability":"stable","signature":"(element: HTMLElement) => void","description":"Unregister a link and clean up its prefetch listeners"} */
61
+ /**
62
+ * Unregister a link from prefetching
63
+ */
64
+ unregisterLink(element) {
65
+ const href = element.getAttribute('href');
66
+ if (href) {
67
+ this.cancelPrefetch(href);
68
+ }
69
+ // Clean up hover timer
70
+ const timer = this.hoverTimers.get(element);
71
+ if (timer) {
72
+ clearTimeout(timer);
73
+ this.hoverTimers.delete(element);
74
+ }
75
+ // Clean up intent tracking
76
+ this.intentTracking.delete(element);
77
+ // Call element-specific cleanup (removes event listeners and visibility observer)
78
+ const target = element;
79
+ const cleanup = target.__prefetchCleanup;
80
+ if (cleanup) {
81
+ cleanup();
82
+ delete target.__prefetchCleanup;
83
+ }
84
+ }
85
+ /**
86
+ * Setup hover-based prefetching with intent delay
87
+ */
88
+ setupHoverPrefetch(element, href) {
89
+ const handleMouseEnter = () => {
90
+ const timer = setTimeout(() => {
91
+ this.prefetchRoute(href, 'hover');
92
+ this.hoverTimers.delete(element);
93
+ }, this.config.hoverDelay);
94
+ this.hoverTimers.set(element, timer);
95
+ };
96
+ const handleMouseLeave = () => {
97
+ // Only clear the hover timer, but DON'T cancel in-flight prefetch requests
98
+ // This is important because mouseleave fires before click, so canceling
99
+ // would abort requests that navigation needs
100
+ const timer = this.hoverTimers.get(element);
101
+ if (timer) {
102
+ clearTimeout(timer);
103
+ this.hoverTimers.delete(element);
104
+ }
105
+ };
106
+ element.addEventListener('mouseenter', handleMouseEnter);
107
+ element.addEventListener('mouseleave', handleMouseLeave);
108
+ // Store cleanup functions
109
+ element.__prefetchCleanup = () => {
110
+ element.removeEventListener('mouseenter', handleMouseEnter);
111
+ element.removeEventListener('mouseleave', handleMouseLeave);
112
+ };
113
+ }
114
+ /**
115
+ * Setup visibility-based prefetching using shared observer pool from @pithyjs/shared
116
+ */
117
+ setupVisiblePrefetch(element) {
118
+ const href = element.getAttribute('href');
119
+ if (!href)
120
+ return;
121
+ // Use shared IntersectionObserver pool
122
+ const cleanup = observeVisibilityOnce(element, () => this.prefetchRoute(href, 'visible'), { rootMargin: this.config.visibleMargin });
123
+ // Store cleanup for unregisterLink
124
+ element.__prefetchCleanup = cleanup;
125
+ }
126
+ /**
127
+ * Setup intent-based prefetching with pointer tracking
128
+ */
129
+ setupIntentPrefetch(element, href) {
130
+ const tracker = new PointerTracker(this.config.intentVelocityThreshold, this.config.intentDirectionSamples);
131
+ // Define proximity threshold (distance from link to start tracking)
132
+ const proximityThreshold = 200; // pixels
133
+ const handleMouseMove = (event) => {
134
+ const rect = element.getBoundingClientRect();
135
+ const centerX = rect.left + rect.width / 2;
136
+ const centerY = rect.top + rect.height / 2;
137
+ // Calculate distance from cursor to link center
138
+ const distanceToLink = Math.sqrt(Math.pow(event.clientX - centerX, 2) +
139
+ Math.pow(event.clientY - centerY, 2));
140
+ // Only track intent if cursor is within proximity threshold
141
+ if (distanceToLink > proximityThreshold) {
142
+ return; // Too far away, ignore
143
+ }
144
+ const isMovingToward = tracker.update(event.clientX, event.clientY, centerX, centerY);
145
+ if (isMovingToward) {
146
+ this.prefetchRoute(href, 'intent');
147
+ // Remove listener after successful prefetch to avoid repeated triggers
148
+ document.removeEventListener('mousemove', handleMouseMove);
149
+ }
150
+ };
151
+ // Listen on document for global mouse movement
152
+ document.addEventListener('mousemove', handleMouseMove);
153
+ this.intentTracking.set(element, tracker);
154
+ // Store cleanup function
155
+ element.__prefetchCleanup = () => {
156
+ document.removeEventListener('mousemove', handleMouseMove);
157
+ };
158
+ }
159
+ /** @codexApi {"parent":"pithy.router.prefetch","name":"prefetchRoute","stability":"stable","signature":"(href: string, strategy: PrefetchStrategy) => Promise<void>","description":"Prefetch a route with priority handling and deduplication"} */
160
+ /**
161
+ * Prefetch a route with priority handling
162
+ */
163
+ async prefetchRoute(href, _strategy) {
164
+ // Don't prefetch if already in progress
165
+ if (this.activeRequests.has(href)) {
166
+ return;
167
+ }
168
+ // Don't prefetch if we're already on this route
169
+ const router = globalThis.router;
170
+ if (router) {
171
+ const currentPath = router.state?.()?.path || window.location.pathname;
172
+ const targetPath = new URL(href, window.location.origin).pathname;
173
+ if (currentPath === targetPath) {
174
+ return;
175
+ }
176
+ }
177
+ // Don't prefetch if we've recently prefetched this route (within its cache TTL)
178
+ const lastPrefetch = this.prefetchedAt.get(href);
179
+ if (lastPrefetch &&
180
+ Date.now() - lastPrefetch.timestamp < lastPrefetch.ttl) {
181
+ return;
182
+ }
183
+ try {
184
+ const controller = new AbortController();
185
+ this.activeRequests.set(href, controller);
186
+ // Prefetch module code and loader data in parallel for faster loading
187
+ await Promise.all([
188
+ this.prefetchModuleCode(href, controller.signal),
189
+ this.prefetchLoaderData(href, controller.signal),
190
+ ]);
191
+ // Record successful prefetch time with route-specific TTL
192
+ if (!controller.signal.aborted) {
193
+ // Get route's cache TTL if available, otherwise use default
194
+ let ttl = this.config.cacheTTL;
195
+ const router = globalThis.router;
196
+ if (router?.getRouteCacheTTL) {
197
+ const routeTTL = await router.getRouteCacheTTL(href);
198
+ if (routeTTL !== undefined) {
199
+ ttl = routeTTL;
200
+ }
201
+ }
202
+ this.prefetchedAt.set(href, { timestamp: Date.now(), ttl });
203
+ }
204
+ }
205
+ catch (error) {
206
+ if (error instanceof Error && error.name !== 'AbortError') {
207
+ console.warn('[Router] Prefetch failed:', error);
208
+ }
209
+ }
210
+ finally {
211
+ this.activeRequests.delete(href);
212
+ }
213
+ }
214
+ /** @codexApi {"parent":"pithy.router.prefetch","name":"cancelPrefetch","stability":"stable","signature":"(href: string) => void","description":"Cancel an in-flight prefetch for a specific route"} */
215
+ /**
216
+ * Cancel prefetch for a specific route
217
+ */
218
+ cancelPrefetch(href) {
219
+ const controller = this.activeRequests.get(href);
220
+ if (controller) {
221
+ controller.abort();
222
+ this.activeRequests.delete(href);
223
+ }
224
+ }
225
+ /**
226
+ * Prefetch module code (dynamic imports)
227
+ */
228
+ async prefetchModuleCode(href, signal) {
229
+ if (signal.aborted)
230
+ return;
231
+ // Get the global router instance to access route manifest
232
+ const router = globalThis.router;
233
+ if (!router || !router.prefetchRoute) {
234
+ return; // No router or prefetch method available
235
+ }
236
+ try {
237
+ await router.prefetchRoute(href, signal);
238
+ }
239
+ catch {
240
+ // Silently fail - not critical
241
+ }
242
+ }
243
+ /**
244
+ * Prefetch loader data
245
+ */
246
+ async prefetchLoaderData(href, signal) {
247
+ if (signal.aborted)
248
+ return;
249
+ // Get the global router instance to access loader system
250
+ const router = globalThis.router;
251
+ if (!router || !router.prefetchLoader) {
252
+ return; // No router or prefetch loader method available
253
+ }
254
+ try {
255
+ await router.prefetchLoader(href, signal);
256
+ }
257
+ catch {
258
+ // Silently fail - not critical
259
+ }
260
+ }
261
+ /** @codexApi {"parent":"pithy.router.prefetch","name":"dispose","stability":"stable","signature":"() => void","description":"Clean up all prefetch resources, requests, and timers"} */
262
+ /**
263
+ * Cleanup all prefetch resources
264
+ */
265
+ dispose() {
266
+ // Cancel all active requests
267
+ for (const controller of this.activeRequests.values()) {
268
+ controller.abort();
269
+ }
270
+ this.activeRequests.clear();
271
+ // Clear all timers
272
+ for (const timer of this.hoverTimers.values()) {
273
+ clearTimeout(timer);
274
+ }
275
+ this.hoverTimers.clear();
276
+ // Clear intent tracking
277
+ this.intentTracking.clear();
278
+ // Clear prefetch timestamps
279
+ this.prefetchedAt.clear();
280
+ // Note: Visibility observers are cleaned up via __prefetchCleanup when elements are unregistered
281
+ // The shared observer pool in @pithyjs/core handles its own lifecycle
282
+ }
283
+ }
284
+ /**
285
+ * Pointer tracker for intent-based prefetching
286
+ */
287
+ class PointerTracker {
288
+ constructor(velocityThreshold, sampleCount) {
289
+ this.positions = [];
290
+ this.velocityThreshold = velocityThreshold;
291
+ this.sampleCount = sampleCount;
292
+ }
293
+ /**
294
+ * Update pointer position and check if moving toward target
295
+ */
296
+ update(x, y, targetX, targetY) {
297
+ const now = Date.now();
298
+ // Add current position
299
+ this.positions.push({ x, y, time: now });
300
+ // Keep only recent samples
301
+ if (this.positions.length > this.sampleCount) {
302
+ this.positions.shift();
303
+ }
304
+ // Need at least 2 positions to calculate velocity
305
+ if (this.positions.length < 2) {
306
+ return false;
307
+ }
308
+ // Calculate velocity and direction
309
+ const recent = this.positions[this.positions.length - 1];
310
+ const previous = this.positions[this.positions.length - 2];
311
+ if (!recent || !previous)
312
+ return false;
313
+ const deltaTime = recent.time - previous.time;
314
+ if (deltaTime === 0)
315
+ return false;
316
+ const deltaX = recent.x - previous.x;
317
+ const deltaY = recent.y - previous.y;
318
+ const velocity = (Math.sqrt(deltaX * deltaX + deltaY * deltaY) / deltaTime) * 1000; // px/second
319
+ // Check if velocity meets threshold
320
+ if (velocity < this.velocityThreshold) {
321
+ return false;
322
+ }
323
+ // Check if moving toward target
324
+ const toTargetX = targetX - recent.x;
325
+ const toTargetY = targetY - recent.y;
326
+ const dotProduct = deltaX * toTargetX + deltaY * toTargetY;
327
+ return dotProduct > 0; // Moving toward target
328
+ }
329
+ }
330
+ /**
331
+ * Global prefetch manager instance (lazy-initialized to avoid TDZ issues
332
+ * with cross-chunk constants during bundled module evaluation).
333
+ */
334
+ let globalPrefetchManager = null;
335
+ /** @codexApi {"parent":"pithy.router.prefetch","name":"getPrefetchManager","stability":"stable","signature":"() => PrefetchManager","description":"Get the lazy-initialized global prefetch manager"} */
336
+ /**
337
+ * Get the global prefetch manager
338
+ */
339
+ export function getPrefetchManager() {
340
+ if (!globalPrefetchManager) {
341
+ globalPrefetchManager = new PrefetchManager();
342
+ }
343
+ return globalPrefetchManager;
344
+ }
345
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/router/features/prefetch/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /** @codex
2
+ {
3
+ "id": "pithy.router.ux.focus",
4
+ "title": "A11y Focus Management",
5
+ "category": "runtime"
6
+ }
7
+ */
8
+ /**
9
+ * Focus management configuration
10
+ */
11
+ export interface FocusConfig {
12
+ /** Focus selector priority (highest to lowest) */
13
+ focusSelectors?: string[];
14
+ /** Whether to manage focus automatically */
15
+ autoFocus?: boolean;
16
+ /** Fallback element for focus when no targets found */
17
+ focusFallback?: string;
18
+ }
19
+ /**
20
+ * Required version of focus configuration
21
+ */
22
+ export type RequiredFocusConfig = Required<FocusConfig>;
23
+ /**
24
+ * Focus manager for enhanced accessibility
25
+ */
26
+ export declare class FocusManager {
27
+ private config;
28
+ constructor(config?: FocusConfig);
29
+ /**
30
+ * Manage focus after navigation
31
+ */
32
+ manageFocus(options?: {
33
+ preventScroll?: boolean;
34
+ }): void;
35
+ /**
36
+ * Ensure element is programmatically focusable
37
+ */
38
+ private ensureFocusable;
39
+ /**
40
+ * Check if element is visible
41
+ */
42
+ private isVisible;
43
+ /**
44
+ * Check if user is currently focused on an interactive element
45
+ * Prevents focus stealing during navigation
46
+ */
47
+ private isUserOnInteractiveElement;
48
+ /**
49
+ * Update focus configuration
50
+ */
51
+ updateConfig(config: Partial<FocusConfig>): void;
52
+ }
53
+ /**
54
+ * Global focus manager instance
55
+ */
56
+ export declare const focusManager: FocusManager;
57
+ /** @codexApi {"parent":"pithy.router.ux.focus","name":"useFocus","stability":"stable","signature":"(config?: FocusConfig) => FocusManager"} */
58
+ /**
59
+ * Focus utilities
60
+ */
61
+ export declare function useFocus(config?: FocusConfig): FocusManager;
62
+ //# sourceMappingURL=focus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"focus.d.ts","sourceRoot":"","sources":["../../../../src/router/features/ux/focus.ts"],"names":[],"mappings":"AAAA;;;;;;EAME;AAEF;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,kDAAkD;IAClD,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,4CAA4C;IAC5C,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,uDAAuD;IACvD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;AAExD;;GAEG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAsB;gBAExB,MAAM,GAAE,WAAgB;IAgBpC;;OAEG;IACH,WAAW,CAAC,OAAO,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI;IAqCxD;;OAEG;IACH,OAAO,CAAC,eAAe;IAWvB;;OAEG;IACH,OAAO,CAAC,SAAS;IAUjB;;;OAGG;IACH,OAAO,CAAC,0BAA0B;IAmClC;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,IAAI;CAGjD;AAED;;GAEG;AACH,eAAO,MAAM,YAAY,cAAqB,CAAC;AAE/C,+IAA+I;AAC/I;;GAEG;AACH,wBAAgB,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,YAAY,CAK3D"}
@@ -0,0 +1,136 @@
1
+ /** @codex
2
+ {
3
+ "id": "pithy.router.ux.focus",
4
+ "title": "A11y Focus Management",
5
+ "category": "runtime"
6
+ }
7
+ */
8
+ /**
9
+ * Focus manager for enhanced accessibility
10
+ */
11
+ export class FocusManager {
12
+ constructor(config = {}) {
13
+ this.config = {
14
+ focusSelectors: [
15
+ '[data-focus-target]',
16
+ '[autofocus]',
17
+ '[tabindex]',
18
+ 'h1',
19
+ '[role=heading]',
20
+ 'main',
21
+ ],
22
+ autoFocus: true,
23
+ focusFallback: '[data-router-outlet]',
24
+ ...config,
25
+ };
26
+ }
27
+ /**
28
+ * Manage focus after navigation
29
+ */
30
+ manageFocus(options) {
31
+ if (!this.config.autoFocus)
32
+ return;
33
+ // Don't steal focus if user is already on an interactive element
34
+ if (this.isUserOnInteractiveElement()) {
35
+ return;
36
+ }
37
+ // Find the best focus target
38
+ let target = null;
39
+ for (const selector of this.config.focusSelectors) {
40
+ target = document.querySelector(selector);
41
+ if (target && this.isVisible(target)) {
42
+ break;
43
+ }
44
+ }
45
+ // Focus the target if found
46
+ if (target) {
47
+ const el = this.ensureFocusable(target);
48
+ const focusOptions = { preventScroll: true, ...(options ?? {}) };
49
+ el.focus(focusOptions);
50
+ return;
51
+ }
52
+ // Fallback to outlet if no targets found
53
+ if (!target) {
54
+ const outlet = document.querySelector(this.config.focusFallback);
55
+ if (outlet) {
56
+ const el = this.ensureFocusable(outlet);
57
+ el.focus({ preventScroll: true });
58
+ return;
59
+ }
60
+ }
61
+ }
62
+ /**
63
+ * Ensure element is programmatically focusable
64
+ */
65
+ ensureFocusable(el) {
66
+ const node = el;
67
+ const isNaturallyFocusable = typeof node.focus === 'function' && node.tabIndex >= 0;
68
+ if (!isNaturallyFocusable && !node.hasAttribute('tabindex')) {
69
+ node.setAttribute('tabindex', '-1');
70
+ node.setAttribute('data-router-tmp-tabindex', '');
71
+ }
72
+ return node;
73
+ }
74
+ /**
75
+ * Check if element is visible
76
+ */
77
+ isVisible(element) {
78
+ const rect = element.getBoundingClientRect();
79
+ return (rect.width > 0 &&
80
+ rect.height > 0 &&
81
+ rect.top < window.innerHeight &&
82
+ rect.bottom > 0);
83
+ }
84
+ /**
85
+ * Check if user is currently focused on an interactive element
86
+ * Prevents focus stealing during navigation
87
+ */
88
+ isUserOnInteractiveElement() {
89
+ const activeEl = document.activeElement;
90
+ if (!activeEl || activeEl === document.body) {
91
+ return false;
92
+ }
93
+ // Check if the active element is an interactive element
94
+ const tagName = activeEl.tagName.toLowerCase();
95
+ const interactiveTags = ['input', 'textarea', 'select', 'button', 'a'];
96
+ // Check tag name
97
+ if (interactiveTags.includes(tagName)) {
98
+ return true;
99
+ }
100
+ // Check if element has tabindex (is focusable)
101
+ if (activeEl.hasAttribute('tabindex')) {
102
+ const tabindex = activeEl.getAttribute('tabindex');
103
+ // tabindex >= 0 means it's in the tab order (interactive)
104
+ if (tabindex !== null && parseInt(tabindex, 10) >= 0) {
105
+ return true;
106
+ }
107
+ }
108
+ // Check for contenteditable
109
+ if (activeEl.hasAttribute('contenteditable') &&
110
+ activeEl.getAttribute('contenteditable') !== 'false') {
111
+ return true;
112
+ }
113
+ return false;
114
+ }
115
+ /**
116
+ * Update focus configuration
117
+ */
118
+ updateConfig(config) {
119
+ this.config = { ...this.config, ...config };
120
+ }
121
+ }
122
+ /**
123
+ * Global focus manager instance
124
+ */
125
+ export const focusManager = new FocusManager();
126
+ /** @codexApi {"parent":"pithy.router.ux.focus","name":"useFocus","stability":"stable","signature":"(config?: FocusConfig) => FocusManager"} */
127
+ /**
128
+ * Focus utilities
129
+ */
130
+ export function useFocus(config) {
131
+ if (config) {
132
+ focusManager.updateConfig(config);
133
+ }
134
+ return focusManager;
135
+ }
136
+ //# sourceMappingURL=focus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"focus.js","sourceRoot":"","sources":["../../../../src/router/features/ux/focus.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ /** @codex
2
+ {
3
+ "id": "pithy.router.ux.live-region",
4
+ "title": "ARIA Live Region Announcements",
5
+ "category": "runtime"
6
+ }
7
+ */
8
+ /**
9
+ * Live region configuration
10
+ */
11
+ export interface LiveRegionConfig {
12
+ /** Enable accessibility announcements (default: true) */
13
+ enabled?: boolean;
14
+ /** Whether to announce route changes (default: true) */
15
+ announceChanges?: boolean;
16
+ /** Politeness level for announcements */
17
+ politeness?: 'polite' | 'assertive';
18
+ /** Duration to keep announcement visible (ms) */
19
+ clearDelay?: number;
20
+ }
21
+ /**
22
+ * Required version of live region configuration
23
+ */
24
+ export type RequiredLiveRegionConfig = Required<LiveRegionConfig>;
25
+ /**
26
+ * Live region manager for screen reader announcements
27
+ */
28
+ export declare class LiveRegionManager {
29
+ private announcer;
30
+ private config;
31
+ constructor(config?: LiveRegionConfig);
32
+ /**
33
+ * Announce route change for screen readers
34
+ */
35
+ announceRouteChange(path?: string): void;
36
+ /**
37
+ * Announce custom message
38
+ */
39
+ announce(message: string, options?: {
40
+ clearDelay?: number;
41
+ }): void;
42
+ /**
43
+ * Announce loading state
44
+ */
45
+ announceLoading(message?: string): void;
46
+ /**
47
+ * Announce navigation error
48
+ */
49
+ announceError(message: string): void;
50
+ /**
51
+ * Announce guard denial
52
+ */
53
+ announceGuardDenied(message?: string): void;
54
+ /**
55
+ * Setup screen reader announcer element
56
+ */
57
+ private setupAnnouncer;
58
+ /**
59
+ * Update live region configuration
60
+ */
61
+ updateConfig(config: Partial<LiveRegionConfig>): void;
62
+ /**
63
+ * Destroy announcer element
64
+ */
65
+ destroy(): void;
66
+ }
67
+ /**
68
+ * Global live region manager instance
69
+ */
70
+ export declare const liveRegionManager: LiveRegionManager;
71
+ /** @codexApi {"parent":"pithy.router.ux.live-region","name":"useLiveRegion","stability":"stable","signature":"(config?: LiveRegionConfig) => LiveRegionManager"} */
72
+ /**
73
+ * Live region utilities
74
+ */
75
+ export declare function useLiveRegion(config?: LiveRegionConfig): LiveRegionManager;
76
+ /** @codexApi {"parent":"pithy.router.ux.live-region","name":"announce","stability":"stable","signature":"(message: string) => void"} */
77
+ /**
78
+ * Announce navigation change
79
+ */
80
+ export declare function announce(message: string): void;
81
+ //# sourceMappingURL=live-region.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live-region.d.ts","sourceRoot":"","sources":["../../../../src/router/features/ux/live-region.ts"],"names":[],"mappings":"AAAA;;;;;;EAME;AAIF;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,yDAAyD;IACzD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,wDAAwD;IACxD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,yCAAyC;IACzC,UAAU,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;IACpC,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC,gBAAgB,CAAC,CAAC;AAElE;;GAEG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,SAAS,CAA4B;IAC7C,OAAO,CAAC,MAAM,CAA2B;gBAE7B,MAAM,GAAE,gBAAqB;IAezC;;OAEG;IACH,mBAAmB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI;IAiBxC;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAclE;;OAEG;IACH,eAAe,CAAC,OAAO,GAAE,MAAqB,GAAG,IAAI;IAKrD;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAYpC;;OAEG;IACH,mBAAmB,CAAC,OAAO,GAAE,MAA6B,GAAG,IAAI;IAYjE;;OAEG;IACH,OAAO,CAAC,cAAc;IAetB;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,IAAI;IA0BrD;;OAEG;IACH,OAAO,IAAI,IAAI;CAMhB;AAED;;GAEG;AACH,eAAO,MAAM,iBAAiB,mBAA0B,CAAC;AAEzD,oKAAoK;AACpK;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,gBAAgB,GAAG,iBAAiB,CAK1E;AAED,wIAAwI;AACxI;;GAEG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE9C"}