@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,166 @@
1
+ /** @codex
2
+ {
3
+ "id": "pithy.router.ux.live-region",
4
+ "title": "ARIA Live Region Announcements",
5
+ "category": "runtime"
6
+ }
7
+ */
8
+ import { SCROLL_FOCUS_DEFAULTS } from '../../core/constants';
9
+ /**
10
+ * Live region manager for screen reader announcements
11
+ */
12
+ export class LiveRegionManager {
13
+ constructor(config = {}) {
14
+ this.announcer = null;
15
+ this.config = {
16
+ enabled: config.enabled !== undefined ? config.enabled : true,
17
+ announceChanges: config.announceChanges !== undefined ? config.announceChanges : true,
18
+ politeness: config.politeness || 'polite',
19
+ clearDelay: config.clearDelay || 1000,
20
+ };
21
+ // Only setup announcer if enabled
22
+ if (this.config.enabled) {
23
+ this.setupAnnouncer();
24
+ }
25
+ }
26
+ /**
27
+ * Announce route change for screen readers
28
+ */
29
+ announceRouteChange(path) {
30
+ if (!this.config.enabled || !this.announcer || !this.config.announceChanges)
31
+ return;
32
+ const currentPath = path || window.location.pathname;
33
+ const announcement = `Navigated to ${currentPath}`;
34
+ this.announcer.textContent = announcement;
35
+ // Clear after announcement
36
+ setTimeout(() => {
37
+ if (this.announcer) {
38
+ this.announcer.textContent = '';
39
+ }
40
+ }, this.config.clearDelay);
41
+ }
42
+ /**
43
+ * Announce custom message
44
+ */
45
+ announce(message, options) {
46
+ if (!this.config.enabled || !this.announcer)
47
+ return;
48
+ this.announcer.textContent = message;
49
+ // Clear after delay
50
+ const delay = options?.clearDelay ?? this.config.clearDelay;
51
+ setTimeout(() => {
52
+ if (this.announcer) {
53
+ this.announcer.textContent = '';
54
+ }
55
+ }, delay);
56
+ }
57
+ /**
58
+ * Announce loading state
59
+ */
60
+ announceLoading(message = 'Loading...') {
61
+ if (!this.config.enabled || !this.announcer)
62
+ return;
63
+ this.announcer.textContent = message;
64
+ }
65
+ /**
66
+ * Announce navigation error
67
+ */
68
+ announceError(message) {
69
+ if (!this.config.enabled || !this.announcer)
70
+ return;
71
+ this.announcer.textContent = message;
72
+ // Clear after delay
73
+ setTimeout(() => {
74
+ if (this.announcer) {
75
+ this.announcer.textContent = '';
76
+ }
77
+ }, this.config.clearDelay);
78
+ }
79
+ /**
80
+ * Announce guard denial
81
+ */
82
+ announceGuardDenied(message = 'Navigation blocked') {
83
+ if (!this.config.enabled || !this.announcer)
84
+ return;
85
+ this.announcer.textContent = message;
86
+ // Clear after delay
87
+ setTimeout(() => {
88
+ if (this.announcer) {
89
+ this.announcer.textContent = '';
90
+ }
91
+ }, this.config.clearDelay);
92
+ }
93
+ /**
94
+ * Setup screen reader announcer element
95
+ */
96
+ setupAnnouncer() {
97
+ if (typeof document === 'undefined')
98
+ return;
99
+ this.announcer = document.createElement('div');
100
+ this.announcer.setAttribute('aria-live', this.config.politeness);
101
+ this.announcer.setAttribute('aria-atomic', 'true');
102
+ this.announcer.style.position = 'absolute';
103
+ this.announcer.style.left = `${SCROLL_FOCUS_DEFAULTS.ANNOUNCER_OFFSET}px`;
104
+ this.announcer.style.width = '1px';
105
+ this.announcer.style.height = '1px';
106
+ this.announcer.style.overflow = 'hidden';
107
+ document.body.appendChild(this.announcer);
108
+ }
109
+ /**
110
+ * Update live region configuration
111
+ */
112
+ updateConfig(config) {
113
+ const wasEnabled = this.config.enabled;
114
+ this.config = { ...this.config, ...config };
115
+ // If enabled state changed
116
+ if (config.enabled !== undefined && config.enabled !== wasEnabled) {
117
+ if (config.enabled && !this.announcer) {
118
+ // Enable: create announcer
119
+ this.setupAnnouncer();
120
+ }
121
+ else if (!config.enabled && this.announcer) {
122
+ // Disable: destroy announcer
123
+ this.destroy();
124
+ }
125
+ }
126
+ // Handle case where enabled is true but announcer was removed externally
127
+ if (this.config.enabled && !this.announcer) {
128
+ this.setupAnnouncer();
129
+ }
130
+ // Update politeness if changed and announcer exists
131
+ if (config.politeness && this.announcer) {
132
+ this.announcer.setAttribute('aria-live', config.politeness);
133
+ }
134
+ }
135
+ /**
136
+ * Destroy announcer element
137
+ */
138
+ destroy() {
139
+ if (this.announcer && this.announcer.parentNode) {
140
+ this.announcer.parentNode.removeChild(this.announcer);
141
+ this.announcer = null;
142
+ }
143
+ }
144
+ }
145
+ /**
146
+ * Global live region manager instance
147
+ */
148
+ export const liveRegionManager = new LiveRegionManager();
149
+ /** @codexApi {"parent":"pithy.router.ux.live-region","name":"useLiveRegion","stability":"stable","signature":"(config?: LiveRegionConfig) => LiveRegionManager"} */
150
+ /**
151
+ * Live region utilities
152
+ */
153
+ export function useLiveRegion(config) {
154
+ if (config) {
155
+ liveRegionManager.updateConfig(config);
156
+ }
157
+ return liveRegionManager;
158
+ }
159
+ /** @codexApi {"parent":"pithy.router.ux.live-region","name":"announce","stability":"stable","signature":"(message: string) => void"} */
160
+ /**
161
+ * Announce navigation change
162
+ */
163
+ export function announce(message) {
164
+ liveRegionManager.announce(message);
165
+ }
166
+ //# sourceMappingURL=live-region.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live-region.js","sourceRoot":"","sources":["../../../../src/router/features/ux/live-region.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,105 @@
1
+ /** @codex
2
+ {
3
+ "id": "pithy.router.scroll-focus",
4
+ "title": "Enhanced Scroll Restoration and Focus Management",
5
+ "category": "runtime"
6
+ }
7
+ */
8
+ /**
9
+ * This file re-exports the separated scroll, focus, and live-region modules
10
+ * as a unified API for convenience.
11
+ *
12
+ * You can also import from the individual modules:
13
+ * - import { ScrollManager, scrollManager } from './scroll';
14
+ * - import { FocusManager, focusManager } from './focus';
15
+ * - import { LiveRegionManager, liveRegionManager } from './live-region';
16
+ */
17
+ import { type ScrollConfig } from './scroll';
18
+ import { type FocusConfig } from './focus';
19
+ import { type LiveRegionConfig } from './live-region';
20
+ /** @codexApi {"parent":"pithy.router.scroll-focus","name":"ScrollFocusConfig","stability":"stable","signature":"interface ScrollFocusConfig"} */
21
+ /**
22
+ * Combined scroll and focus configuration
23
+ * Allows customization of scroll restoration, focus management, and a11y announcements
24
+ */
25
+ export interface ScrollFocusConfig {
26
+ scroll?: ScrollConfig;
27
+ focus?: FocusConfig;
28
+ liveRegion?: LiveRegionConfig;
29
+ }
30
+ /** @codexApi {"parent":"pithy.router.scroll-focus","name":"ScrollFocusManager","stability":"stable","signature":"class ScrollFocusManager"} */
31
+ /**
32
+ * Combined scroll and focus manager for enhanced navigation UX
33
+ * Handles scroll restoration, focus management, and accessibility announcements during navigation.
34
+ * Integrates scroll position saving, intelligent focus targeting, and screen reader notifications.
35
+ */
36
+ export declare class ScrollFocusManager {
37
+ private scrollMgr;
38
+ private focusMgr;
39
+ private liveRegionMgr;
40
+ constructor(config?: ScrollFocusConfig);
41
+ /** @codexApi {"parent":"pithy.router.scroll-focus","name":"scrollToTop","stability":"stable","signature":"(options?: { behavior?: ScrollBehavior }) => void"} */
42
+ /**
43
+ * Scroll to top of page
44
+ * Smoothly scrolls to the top of the document.
45
+ */
46
+ scrollToTop(options?: {
47
+ behavior?: ScrollBehavior;
48
+ }): void;
49
+ /** @codexApi {"parent":"pithy.router.scroll-focus","name":"scrollToAnchor","stability":"stable","signature":"(hash: string, options?: { delay?: number; retries?: number; behavior?: ScrollBehavior }) => Promise<void>"} */
50
+ /**
51
+ * Scroll to anchor with retry logic for CSS loading delays
52
+ * Intelligently scrolls to hash anchors with retries for elements that may not be rendered yet.
53
+ */
54
+ scrollToAnchor(hash: string, options?: {
55
+ delay?: number;
56
+ retries?: number;
57
+ behavior?: ScrollBehavior;
58
+ }): Promise<void>;
59
+ /** @codexApi {"parent":"pithy.router.scroll-focus","name":"manageFocus","stability":"stable","signature":"(options?: { preventScroll?: boolean }) => void"} */
60
+ /**
61
+ * Manage focus after navigation
62
+ * Intelligently manages keyboard focus and triggers screen reader announcements.
63
+ */
64
+ manageFocus(options?: {
65
+ preventScroll?: boolean;
66
+ }): void;
67
+ /**
68
+ * Restore scroll position from history.state
69
+ * Used for popstate navigation to restore scroll from browser history state.
70
+ * @internal Used internally by the router during back/forward navigation
71
+ */
72
+ restoreFromHistory(options?: {
73
+ behavior?: ScrollBehavior;
74
+ navId?: number;
75
+ }): void;
76
+ /**
77
+ * Update configuration
78
+ */
79
+ updateConfig(config: Partial<ScrollFocusConfig>): void;
80
+ /** @codexApi {"parent":"pithy.router.scroll-focus","name":"announceLoading","stability":"stable","signature":"(message?: string) => void"} */
81
+ /**
82
+ * Announce loading state for screen readers
83
+ */
84
+ announceLoading(message?: string): void;
85
+ /** @codexApi {"parent":"pithy.router.scroll-focus","name":"announceError","stability":"stable","signature":"(message: string) => void"} */
86
+ /**
87
+ * Announce navigation error for screen readers
88
+ */
89
+ announceError(message: string): void;
90
+ /** @codexApi {"parent":"pithy.router.scroll-focus","name":"announceGuardDenied","stability":"stable","signature":"(message?: string) => void"} */
91
+ /**
92
+ * Announce guard denial for screen readers
93
+ */
94
+ announceGuardDenied(message?: string): void;
95
+ }
96
+ /**
97
+ * Global scroll and focus manager instance
98
+ * Singleton instance used by the router for scroll and focus management.
99
+ * @internal Used internally by the router; users typically don't access this directly
100
+ */
101
+ export declare const scrollFocusManager: ScrollFocusManager;
102
+ export { ScrollManager, scrollManager, useScroll, type ScrollConfig, } from './scroll';
103
+ export { FocusManager, focusManager, useFocus, type FocusConfig, } from './focus';
104
+ export { LiveRegionManager, liveRegionManager, useLiveRegion, announce, type LiveRegionConfig, } from './live-region';
105
+ //# sourceMappingURL=scroll-focus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scroll-focus.d.ts","sourceRoot":"","sources":["../../../../src/router/features/ux/scroll-focus.ts"],"names":[],"mappings":"AAAA;;;;;;EAME;AAEF;;;;;;;;GAQG;AAEH,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAC5D,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AACzD,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEzE,iJAAiJ;AACjJ;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AAED,+IAA+I;AAC/I;;;;GAIG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,aAAa,CAAoB;gBAE7B,MAAM,GAAE,iBAAsB;IAM1C,iKAAiK;IACjK;;;OAGG;IACH,WAAW,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,cAAc,CAAA;KAAE,GAAG,IAAI;IAI1D,6NAA6N;IAC7N;;;OAGG;IACG,cAAc,CAClB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QACR,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,cAAc,CAAC;KAC3B,GACA,OAAO,CAAC,IAAI,CAAC;IAIhB,+JAA+J;IAC/J;;;OAGG;IACH,WAAW,CAAC,OAAO,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI;IAKxD;;;;OAIG;IACH,kBAAkB,CAAC,OAAO,CAAC,EAAE;QAC3B,QAAQ,CAAC,EAAE,cAAc,CAAC;QAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GAAG,IAAI;IAIR;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,IAAI;IAYtD,8IAA8I;IAC9I;;OAEG;IACH,eAAe,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAIvC,2IAA2I;IAC3I;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAIpC,kJAAkJ;IAClJ;;OAEG;IACH,mBAAmB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;CAG5C;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,oBAA2B,CAAC;AAG3D,OAAO,EACL,aAAa,EACb,aAAa,EACb,SAAS,EACT,KAAK,YAAY,GAClB,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,QAAQ,EACR,KAAK,WAAW,GACjB,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,QAAQ,EACR,KAAK,gBAAgB,GACtB,MAAM,eAAe,CAAC"}
@@ -0,0 +1,111 @@
1
+ /** @codex
2
+ {
3
+ "id": "pithy.router.scroll-focus",
4
+ "title": "Enhanced Scroll Restoration and Focus Management",
5
+ "category": "runtime"
6
+ }
7
+ */
8
+ /**
9
+ * This file re-exports the separated scroll, focus, and live-region modules
10
+ * as a unified API for convenience.
11
+ *
12
+ * You can also import from the individual modules:
13
+ * - import { ScrollManager, scrollManager } from './scroll';
14
+ * - import { FocusManager, focusManager } from './focus';
15
+ * - import { LiveRegionManager, liveRegionManager } from './live-region';
16
+ */
17
+ import { ScrollManager } from './scroll';
18
+ import { FocusManager } from './focus';
19
+ import { LiveRegionManager } from './live-region';
20
+ /** @codexApi {"parent":"pithy.router.scroll-focus","name":"ScrollFocusManager","stability":"stable","signature":"class ScrollFocusManager"} */
21
+ /**
22
+ * Combined scroll and focus manager for enhanced navigation UX
23
+ * Handles scroll restoration, focus management, and accessibility announcements during navigation.
24
+ * Integrates scroll position saving, intelligent focus targeting, and screen reader notifications.
25
+ */
26
+ export class ScrollFocusManager {
27
+ constructor(config = {}) {
28
+ this.scrollMgr = new ScrollManager(config.scroll);
29
+ this.focusMgr = new FocusManager(config.focus);
30
+ this.liveRegionMgr = new LiveRegionManager(config.liveRegion);
31
+ }
32
+ /** @codexApi {"parent":"pithy.router.scroll-focus","name":"scrollToTop","stability":"stable","signature":"(options?: { behavior?: ScrollBehavior }) => void"} */
33
+ /**
34
+ * Scroll to top of page
35
+ * Smoothly scrolls to the top of the document.
36
+ */
37
+ scrollToTop(options) {
38
+ this.scrollMgr.scrollToTop(options);
39
+ }
40
+ /** @codexApi {"parent":"pithy.router.scroll-focus","name":"scrollToAnchor","stability":"stable","signature":"(hash: string, options?: { delay?: number; retries?: number; behavior?: ScrollBehavior }) => Promise<void>"} */
41
+ /**
42
+ * Scroll to anchor with retry logic for CSS loading delays
43
+ * Intelligently scrolls to hash anchors with retries for elements that may not be rendered yet.
44
+ */
45
+ async scrollToAnchor(hash, options) {
46
+ return this.scrollMgr.scrollToAnchor(hash, options);
47
+ }
48
+ /** @codexApi {"parent":"pithy.router.scroll-focus","name":"manageFocus","stability":"stable","signature":"(options?: { preventScroll?: boolean }) => void"} */
49
+ /**
50
+ * Manage focus after navigation
51
+ * Intelligently manages keyboard focus and triggers screen reader announcements.
52
+ */
53
+ manageFocus(options) {
54
+ this.focusMgr.manageFocus(options);
55
+ this.liveRegionMgr.announceRouteChange();
56
+ }
57
+ /**
58
+ * Restore scroll position from history.state
59
+ * Used for popstate navigation to restore scroll from browser history state.
60
+ * @internal Used internally by the router during back/forward navigation
61
+ */
62
+ restoreFromHistory(options) {
63
+ this.scrollMgr.restoreFromHistory(options);
64
+ }
65
+ /**
66
+ * Update configuration
67
+ */
68
+ updateConfig(config) {
69
+ if (config.scroll) {
70
+ this.scrollMgr.updateConfig(config.scroll);
71
+ }
72
+ if (config.focus) {
73
+ this.focusMgr.updateConfig(config.focus);
74
+ }
75
+ if (config.liveRegion) {
76
+ this.liveRegionMgr.updateConfig(config.liveRegion);
77
+ }
78
+ }
79
+ /** @codexApi {"parent":"pithy.router.scroll-focus","name":"announceLoading","stability":"stable","signature":"(message?: string) => void"} */
80
+ /**
81
+ * Announce loading state for screen readers
82
+ */
83
+ announceLoading(message) {
84
+ this.liveRegionMgr.announceLoading(message);
85
+ }
86
+ /** @codexApi {"parent":"pithy.router.scroll-focus","name":"announceError","stability":"stable","signature":"(message: string) => void"} */
87
+ /**
88
+ * Announce navigation error for screen readers
89
+ */
90
+ announceError(message) {
91
+ this.liveRegionMgr.announceError(message);
92
+ }
93
+ /** @codexApi {"parent":"pithy.router.scroll-focus","name":"announceGuardDenied","stability":"stable","signature":"(message?: string) => void"} */
94
+ /**
95
+ * Announce guard denial for screen readers
96
+ */
97
+ announceGuardDenied(message) {
98
+ this.liveRegionMgr.announceGuardDenied(message);
99
+ }
100
+ }
101
+ /**
102
+ * Global scroll and focus manager instance
103
+ * Singleton instance used by the router for scroll and focus management.
104
+ * @internal Used internally by the router; users typically don't access this directly
105
+ */
106
+ export const scrollFocusManager = new ScrollFocusManager();
107
+ // Re-export individual modules for direct use
108
+ export { ScrollManager, scrollManager, useScroll, } from './scroll';
109
+ export { FocusManager, focusManager, useFocus, } from './focus';
110
+ export { LiveRegionManager, liveRegionManager, useLiveRegion, announce, } from './live-region';
111
+ //# sourceMappingURL=scroll-focus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scroll-focus.js","sourceRoot":"","sources":["../../../../src/router/features/ux/scroll-focus.ts"],"names":[],"mappings":"AAAA;;;;;;EAME;AAEF;;;;;;;;GAQG;AAEH,OAAO,EAAE,aAAa,EAAqB,MAAM,UAAU,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAoB,MAAM,SAAS,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAyB,MAAM,eAAe,CAAC;AAazE,+IAA+I;AAC/I;;;;GAIG;AACH,MAAM,OAAO,kBAAkB;IAK7B,YAAY,SAA4B,EAAE;QACxC,IAAI,CAAC,SAAS,GAAG,IAAI,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAClD,IAAI,CAAC,QAAQ,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC/C,IAAI,CAAC,aAAa,GAAG,IAAI,iBAAiB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAChE,CAAC;IAED,iKAAiK;IACjK;;;OAGG;IACH,WAAW,CAAC,OAAuC;QACjD,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACtC,CAAC;IAED,6NAA6N;IAC7N;;;OAGG;IACH,KAAK,CAAC,cAAc,CAClB,IAAY,EACZ,OAIC;QAED,OAAO,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACtD,CAAC;IAED,+JAA+J;IAC/J;;;OAGG;IACH,WAAW,CAAC,OAAqC;QAC/C,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QACnC,IAAI,CAAC,aAAa,CAAC,mBAAmB,EAAE,CAAC;IAC3C,CAAC;IAED;;;;OAIG;IACH,kBAAkB,CAAC,OAGlB;QACC,IAAI,CAAC,SAAS,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAC7C,CAAC;IAED;;OAEG;IACH,YAAY,CAAC,MAAkC;QAC7C,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClB,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC7C,CAAC;QACD,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YACjB,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC3C,CAAC;QACD,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;YACtB,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;IAED,8IAA8I;IAC9I;;OAEG;IACH,eAAe,CAAC,OAAgB;QAC9B,IAAI,CAAC,aAAa,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;IAC9C,CAAC;IAED,2IAA2I;IAC3I;;OAEG;IACH,aAAa,CAAC,OAAe;QAC3B,IAAI,CAAC,aAAa,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAC5C,CAAC;IAED,kJAAkJ;IAClJ;;OAEG;IACH,mBAAmB,CAAC,OAAgB;QAClC,IAAI,CAAC,aAAa,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAClD,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,kBAAkB,EAAE,CAAC;AAE3D,8CAA8C;AAC9C,OAAO,EACL,aAAa,EACb,aAAa,EACb,SAAS,GAEV,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,QAAQ,GAET,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,QAAQ,GAET,MAAM,eAAe,CAAC"}
@@ -0,0 +1,71 @@
1
+ /** @codex
2
+ {
3
+ "id": "pithy.router.ux.scroll",
4
+ "title": "Scroll Restoration Manager",
5
+ "category": "runtime"
6
+ }
7
+ */
8
+ /**
9
+ * Scroll restoration configuration
10
+ */
11
+ export interface ScrollConfig {
12
+ /** Whether to restore scroll position on back/forward navigation */
13
+ restoreOnPop?: boolean;
14
+ /** Whether to scroll to top on forward navigation */
15
+ topOnPush?: boolean;
16
+ /** Whether to preserve scroll position for specific routes */
17
+ preserveScroll?: boolean;
18
+ /** Delay before scrolling to anchor targets (ms) */
19
+ anchorDelay?: number;
20
+ /** Maximum retries for anchor scrolling */
21
+ anchorRetries?: number;
22
+ /** Anchor scroll behavior */
23
+ anchorBehavior?: ScrollBehavior;
24
+ }
25
+ /**
26
+ * Required version of scroll configuration
27
+ */
28
+ export type RequiredScrollConfig = Required<ScrollConfig>;
29
+ /**
30
+ * Scroll manager for enhanced scroll restoration
31
+ */
32
+ export declare class ScrollManager {
33
+ private config;
34
+ constructor(config?: ScrollConfig);
35
+ /**
36
+ * Scroll to top of page
37
+ */
38
+ scrollToTop(options?: {
39
+ behavior?: ScrollBehavior;
40
+ }): void;
41
+ /**
42
+ * Scroll to anchor with retry logic for CSS loading delays
43
+ */
44
+ scrollToAnchor(hash: string, options?: {
45
+ delay?: number;
46
+ retries?: number;
47
+ behavior?: ScrollBehavior;
48
+ }): Promise<void>;
49
+ /**
50
+ * Restore scroll position from history.state (production-ready manual restoration)
51
+ * Implements wait-until-tall-enough logic for loader-based routes
52
+ */
53
+ restoreFromHistory(options?: {
54
+ behavior?: ScrollBehavior;
55
+ navId?: number;
56
+ }): void;
57
+ /**
58
+ * Update scroll configuration
59
+ */
60
+ updateConfig(config: Partial<ScrollConfig>): void;
61
+ }
62
+ /**
63
+ * Global scroll manager instance
64
+ */
65
+ export declare const scrollManager: ScrollManager;
66
+ /** @codexApi {"parent":"pithy.router.ux.scroll","name":"useScroll","stability":"stable","signature":"(config?: ScrollConfig) => ScrollManager"} */
67
+ /**
68
+ * Scroll utilities
69
+ */
70
+ export declare function useScroll(config?: ScrollConfig): ScrollManager;
71
+ //# sourceMappingURL=scroll.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scroll.d.ts","sourceRoot":"","sources":["../../../../src/router/features/ux/scroll.ts"],"names":[],"mappings":"AAAA;;;;;;EAME;AAIF;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,oEAAoE;IACpE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,qDAAqD;IACrD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,8DAA8D;IAC9D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6BAA6B;IAC7B,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC;AAE1D;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAuB;gBAEzB,MAAM,GAAE,YAAiB;IAYrC;;OAEG;IACH,WAAW,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,cAAc,CAAA;KAAE,GAAG,IAAI;IAQ1D;;OAEG;IACG,cAAc,CAClB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QACR,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,cAAc,CAAC;KAC3B,GACA,OAAO,CAAC,IAAI,CAAC;IAoChB;;;OAGG;IACH,kBAAkB,CAAC,OAAO,CAAC,EAAE;QAC3B,QAAQ,CAAC,EAAE,cAAc,CAAC;QAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GAAG,IAAI;IAyDR;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,IAAI;CAGlD;AAED;;GAEG;AACH,eAAO,MAAM,aAAa,eAAsB,CAAC;AAEjD,mJAAmJ;AACnJ;;GAEG;AACH,wBAAgB,SAAS,CAAC,MAAM,CAAC,EAAE,YAAY,GAAG,aAAa,CAK9D"}
@@ -0,0 +1,137 @@
1
+ /** @codex
2
+ {
3
+ "id": "pithy.router.ux.scroll",
4
+ "title": "Scroll Restoration Manager",
5
+ "category": "runtime"
6
+ }
7
+ */
8
+ /**
9
+ * Scroll manager for enhanced scroll restoration
10
+ */
11
+ export class ScrollManager {
12
+ constructor(config = {}) {
13
+ this.config = {
14
+ restoreOnPop: true,
15
+ topOnPush: true,
16
+ preserveScroll: false,
17
+ anchorDelay: 100,
18
+ anchorRetries: 3,
19
+ anchorBehavior: 'smooth',
20
+ ...config,
21
+ };
22
+ }
23
+ /**
24
+ * Scroll to top of page
25
+ */
26
+ scrollToTop(options) {
27
+ window.scrollTo({
28
+ left: 0,
29
+ top: 0,
30
+ behavior: options?.behavior || 'auto',
31
+ });
32
+ }
33
+ /**
34
+ * Scroll to anchor with retry logic for CSS loading delays
35
+ */
36
+ async scrollToAnchor(hash, options) {
37
+ const delay = options?.delay ?? this.config.anchorDelay;
38
+ const retries = options?.retries ?? this.config.anchorRetries;
39
+ const behavior = options?.behavior ?? this.config.anchorBehavior ?? 'auto';
40
+ let target = null;
41
+ // Try to FIND the element across retries (handles late-mounting anchors)
42
+ for (let attempt = 0; attempt <= retries; attempt++) {
43
+ target = document.querySelector(hash);
44
+ if (target)
45
+ break;
46
+ await new Promise(r => setTimeout(r, delay * (attempt + 1)));
47
+ }
48
+ if (!target) {
49
+ console.warn('[Router] Anchor target not found:', hash);
50
+ return;
51
+ }
52
+ // Now that we have a target, also retry position (handles late CSS/layout)
53
+ for (let attempt = 0; attempt <= retries; attempt++) {
54
+ target.scrollIntoView({ behavior, block: 'start' });
55
+ const rect = target.getBoundingClientRect();
56
+ if (rect.top >= 0 && rect.top <= window.innerHeight) {
57
+ return;
58
+ }
59
+ if (attempt < retries) {
60
+ await new Promise(r => setTimeout(r, delay * (attempt + 1)));
61
+ }
62
+ }
63
+ console.warn('[Router] Failed to scroll to anchor:', hash);
64
+ }
65
+ /**
66
+ * Restore scroll position from history.state (production-ready manual restoration)
67
+ * Implements wait-until-tall-enough logic for loader-based routes
68
+ */
69
+ restoreFromHistory(options) {
70
+ const st = history.state;
71
+ const pkg = st?.__scroll;
72
+ if (!pkg) {
73
+ return;
74
+ }
75
+ const desired = { x: pkg.window?.x ?? 0, y: pkg.window?.y ?? 0 };
76
+ // Wait-until-tall-enough logic with proper abort handling
77
+ const restoreWindow = () => {
78
+ const scroller = document.scrollingElement || document.documentElement;
79
+ let i = 0;
80
+ const maxFrames = 60; // ~1 second at 60fps
81
+ const tick = () => {
82
+ const maxY = scroller.scrollHeight - window.innerHeight;
83
+ if (desired.y <= maxY || i++ >= maxFrames) {
84
+ // Disable overflow anchor to prevent scroll jumping during restoration
85
+ const prevAnchor = document.documentElement.style.overflowAnchor;
86
+ document.documentElement.style.overflowAnchor = 'none';
87
+ window.scrollTo({
88
+ left: desired.x,
89
+ top: desired.y,
90
+ behavior: options?.behavior || 'auto',
91
+ });
92
+ // Re-enable overflow anchor after restoration
93
+ requestAnimationFrame(() => {
94
+ document.documentElement.style.overflowAnchor = prevAnchor || '';
95
+ });
96
+ }
97
+ else {
98
+ // Content not tall enough yet, wait for next frame
99
+ requestAnimationFrame(tick);
100
+ }
101
+ };
102
+ requestAnimationFrame(tick);
103
+ };
104
+ restoreWindow();
105
+ // Restore scrollable containers if any
106
+ if (pkg.containers && Array.isArray(pkg.containers)) {
107
+ pkg.containers.forEach(item => {
108
+ const el = document.querySelector(item.sel);
109
+ if (el) {
110
+ el.scrollLeft = item.x ?? 0;
111
+ el.scrollTop = item.y ?? 0;
112
+ }
113
+ });
114
+ }
115
+ }
116
+ /**
117
+ * Update scroll configuration
118
+ */
119
+ updateConfig(config) {
120
+ this.config = { ...this.config, ...config };
121
+ }
122
+ }
123
+ /**
124
+ * Global scroll manager instance
125
+ */
126
+ export const scrollManager = new ScrollManager();
127
+ /** @codexApi {"parent":"pithy.router.ux.scroll","name":"useScroll","stability":"stable","signature":"(config?: ScrollConfig) => ScrollManager"} */
128
+ /**
129
+ * Scroll utilities
130
+ */
131
+ export function useScroll(config) {
132
+ if (config) {
133
+ scrollManager.updateConfig(config);
134
+ }
135
+ return scrollManager;
136
+ }
137
+ //# sourceMappingURL=scroll.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scroll.js","sourceRoot":"","sources":["../../../../src/router/features/ux/scroll.ts"],"names":[],"mappings":"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"}