@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.
- package/LICENSE +21 -0
- package/README.md +621 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/router/core/config.d.ts +22 -0
- package/dist/router/core/config.d.ts.map +1 -0
- package/dist/router/core/config.js +54 -0
- package/dist/router/core/config.js.map +1 -0
- package/dist/router/core/constants.d.ts +89 -0
- package/dist/router/core/constants.d.ts.map +1 -0
- package/dist/router/core/constants.js +95 -0
- package/dist/router/core/constants.js.map +1 -0
- package/dist/router/core/history.d.ts +54 -0
- package/dist/router/core/history.d.ts.map +1 -0
- package/dist/router/core/history.js +89 -0
- package/dist/router/core/history.js.map +1 -0
- package/dist/router/core/matcher.d.ts +43 -0
- package/dist/router/core/matcher.d.ts.map +1 -0
- package/dist/router/core/matcher.js +242 -0
- package/dist/router/core/matcher.js.map +1 -0
- package/dist/router/core/params.d.ts +88 -0
- package/dist/router/core/params.d.ts.map +1 -0
- package/dist/router/core/params.js +150 -0
- package/dist/router/core/params.js.map +1 -0
- package/dist/router/core/pipeline.d.ts +90 -0
- package/dist/router/core/pipeline.d.ts.map +1 -0
- package/dist/router/core/pipeline.js +323 -0
- package/dist/router/core/pipeline.js.map +1 -0
- package/dist/router/core/route-tree.d.ts +76 -0
- package/dist/router/core/route-tree.d.ts.map +1 -0
- package/dist/router/core/route-tree.js +143 -0
- package/dist/router/core/route-tree.js.map +1 -0
- package/dist/router/core/router.d.ts +229 -0
- package/dist/router/core/router.d.ts.map +1 -0
- package/dist/router/core/router.js +762 -0
- package/dist/router/core/router.js.map +1 -0
- package/dist/router/core/types.d.ts +387 -0
- package/dist/router/core/types.d.ts.map +1 -0
- package/dist/router/core/types.js +22 -0
- package/dist/router/core/types.js.map +1 -0
- package/dist/router/core/utils.d.ts +66 -0
- package/dist/router/core/utils.d.ts.map +1 -0
- package/dist/router/core/utils.js +152 -0
- package/dist/router/core/utils.js.map +1 -0
- package/dist/router/features/coordinator.d.ts +75 -0
- package/dist/router/features/coordinator.d.ts.map +1 -0
- package/dist/router/features/coordinator.js +203 -0
- package/dist/router/features/coordinator.js.map +1 -0
- package/dist/router/features/errors/error-codes.d.ts +107 -0
- package/dist/router/features/errors/error-codes.d.ts.map +1 -0
- package/dist/router/features/errors/error-codes.js +247 -0
- package/dist/router/features/errors/error-codes.js.map +1 -0
- package/dist/router/features/errors/error-resolution.d.ts +31 -0
- package/dist/router/features/errors/error-resolution.d.ts.map +1 -0
- package/dist/router/features/errors/error-resolution.js +47 -0
- package/dist/router/features/errors/error-resolution.js.map +1 -0
- package/dist/router/features/errors/error-types.d.ts +60 -0
- package/dist/router/features/errors/error-types.d.ts.map +1 -0
- package/dist/router/features/errors/error-types.js +57 -0
- package/dist/router/features/errors/error-types.js.map +1 -0
- package/dist/router/features/errors/global-error-fallback.d.ts +33 -0
- package/dist/router/features/errors/global-error-fallback.d.ts.map +1 -0
- package/dist/router/features/errors/global-error-fallback.js +182 -0
- package/dist/router/features/errors/global-error-fallback.js.map +1 -0
- package/dist/router/features/errors/index.d.ts +15 -0
- package/dist/router/features/errors/index.d.ts.map +1 -0
- package/dist/router/features/errors/index.js +18 -0
- package/dist/router/features/errors/index.js.map +1 -0
- package/dist/router/features/errors/router-errors.d.ts +44 -0
- package/dist/router/features/errors/router-errors.d.ts.map +1 -0
- package/dist/router/features/errors/router-errors.js +53 -0
- package/dist/router/features/errors/router-errors.js.map +1 -0
- package/dist/router/features/guards/engine.d.ts +40 -0
- package/dist/router/features/guards/engine.d.ts.map +1 -0
- package/dist/router/features/guards/engine.js +67 -0
- package/dist/router/features/guards/engine.js.map +1 -0
- package/dist/router/features/guards/index.d.ts +52 -0
- package/dist/router/features/guards/index.d.ts.map +1 -0
- package/dist/router/features/guards/index.js +95 -0
- package/dist/router/features/guards/index.js.map +1 -0
- package/dist/router/features/loaders/cache.d.ts +60 -0
- package/dist/router/features/loaders/cache.d.ts.map +1 -0
- package/dist/router/features/loaders/cache.js +80 -0
- package/dist/router/features/loaders/cache.js.map +1 -0
- package/dist/router/features/loaders/engine.d.ts +17 -0
- package/dist/router/features/loaders/engine.d.ts.map +1 -0
- package/dist/router/features/loaders/engine.js +27 -0
- package/dist/router/features/loaders/engine.js.map +1 -0
- package/dist/router/features/loaders/index.d.ts +49 -0
- package/dist/router/features/loaders/index.d.ts.map +1 -0
- package/dist/router/features/loaders/index.js +112 -0
- package/dist/router/features/loaders/index.js.map +1 -0
- package/dist/router/features/loading-ui-manager.d.ts +69 -0
- package/dist/router/features/loading-ui-manager.d.ts.map +1 -0
- package/dist/router/features/loading-ui-manager.js +209 -0
- package/dist/router/features/loading-ui-manager.js.map +1 -0
- package/dist/router/features/module-loader.d.ts +50 -0
- package/dist/router/features/module-loader.d.ts.map +1 -0
- package/dist/router/features/module-loader.js +121 -0
- package/dist/router/features/module-loader.js.map +1 -0
- package/dist/router/features/outlet/errors.d.ts +24 -0
- package/dist/router/features/outlet/errors.d.ts.map +1 -0
- package/dist/router/features/outlet/errors.js +96 -0
- package/dist/router/features/outlet/errors.js.map +1 -0
- package/dist/router/features/outlet/index.d.ts +45 -0
- package/dist/router/features/outlet/index.d.ts.map +1 -0
- package/dist/router/features/outlet/index.js +95 -0
- package/dist/router/features/outlet/index.js.map +1 -0
- package/dist/router/features/outlet/mount.d.ts +38 -0
- package/dist/router/features/outlet/mount.d.ts.map +1 -0
- package/dist/router/features/outlet/mount.js +301 -0
- package/dist/router/features/outlet/mount.js.map +1 -0
- package/dist/router/features/outlet/segment-chain.d.ts +16 -0
- package/dist/router/features/outlet/segment-chain.d.ts.map +1 -0
- package/dist/router/features/outlet/segment-chain.js +62 -0
- package/dist/router/features/outlet/segment-chain.js.map +1 -0
- package/dist/router/features/prefetch/index.d.ts +97 -0
- package/dist/router/features/prefetch/index.d.ts.map +1 -0
- package/dist/router/features/prefetch/index.js +345 -0
- package/dist/router/features/prefetch/index.js.map +1 -0
- package/dist/router/features/ux/focus.d.ts +62 -0
- package/dist/router/features/ux/focus.d.ts.map +1 -0
- package/dist/router/features/ux/focus.js +136 -0
- package/dist/router/features/ux/focus.js.map +1 -0
- package/dist/router/features/ux/live-region.d.ts +81 -0
- package/dist/router/features/ux/live-region.d.ts.map +1 -0
- package/dist/router/features/ux/live-region.js +166 -0
- package/dist/router/features/ux/live-region.js.map +1 -0
- package/dist/router/features/ux/scroll-focus.d.ts +105 -0
- package/dist/router/features/ux/scroll-focus.d.ts.map +1 -0
- package/dist/router/features/ux/scroll-focus.js +111 -0
- package/dist/router/features/ux/scroll-focus.js.map +1 -0
- package/dist/router/features/ux/scroll.d.ts +71 -0
- package/dist/router/features/ux/scroll.d.ts.map +1 -0
- package/dist/router/features/ux/scroll.js +137 -0
- package/dist/router/features/ux/scroll.js.map +1 -0
- package/dist/router/features/ux/transitions.d.ts +120 -0
- package/dist/router/features/ux/transitions.d.ts.map +1 -0
- package/dist/router/features/ux/transitions.js +235 -0
- package/dist/router/features/ux/transitions.js.map +1 -0
- package/dist/router/integration/directive-binder.d.ts +20 -0
- package/dist/router/integration/directive-binder.d.ts.map +1 -0
- package/dist/router/integration/directive-binder.js +139 -0
- package/dist/router/integration/directive-binder.js.map +1 -0
- package/dist/router/integration/directives.d.ts +42 -0
- package/dist/router/integration/directives.d.ts.map +1 -0
- package/dist/router/integration/directives.js +109 -0
- package/dist/router/integration/directives.js.map +1 -0
- package/dist/router/integration/history-adapter.d.ts +76 -0
- package/dist/router/integration/history-adapter.d.ts.map +1 -0
- package/dist/router/integration/history-adapter.js +157 -0
- package/dist/router/integration/history-adapter.js.map +1 -0
- package/dist/router/integration/hmr.d.ts +83 -0
- package/dist/router/integration/hmr.d.ts.map +1 -0
- package/dist/router/integration/hmr.js +160 -0
- package/dist/router/integration/hmr.js.map +1 -0
- package/dist/router/integration/link-interceptor.d.ts +49 -0
- package/dist/router/integration/link-interceptor.d.ts.map +1 -0
- package/dist/router/integration/link-interceptor.js +94 -0
- package/dist/router/integration/link-interceptor.js.map +1 -0
- 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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"}
|