@esmx/router 3.0.0-rc.58 → 3.0.0-rc.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/options.mjs +5 -3
- package/dist/route-task.mjs +1 -4
- package/dist/route-transition.mjs +44 -0
- package/dist/scroll.d.ts +33 -0
- package/dist/scroll.mjs +49 -0
- package/dist/types.d.ts +2 -0
- package/package.json +2 -2
- package/src/options.ts +1 -0
- package/src/route-task.ts +1 -4
- package/src/route-transition.ts +47 -0
- package/src/scroll.ts +108 -0
- package/src/types.ts +2 -0
package/dist/options.mjs
CHANGED
|
@@ -30,7 +30,7 @@ function getBaseUrl(options) {
|
|
|
30
30
|
return base;
|
|
31
31
|
}
|
|
32
32
|
export function parsedOptions(options = {}) {
|
|
33
|
-
var _a, _b, _c, _d, _e, _f;
|
|
33
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
34
34
|
const base = getBaseUrl(options);
|
|
35
35
|
const routes = (_a = options.routes) != null ? _a : [];
|
|
36
36
|
const compiledRoutes = createRouteMatches(routes);
|
|
@@ -50,9 +50,11 @@ export function parsedOptions(options = {}) {
|
|
|
50
50
|
matcher: createMatcher(routes, compiledRoutes),
|
|
51
51
|
normalizeURL: (_c = options.normalizeURL) != null ? _c : (url) => url,
|
|
52
52
|
fallback: (_d = options.fallback) != null ? _d : fallback,
|
|
53
|
-
|
|
53
|
+
nextTick: (_e = options.nextTick) != null ? _e : () => {
|
|
54
54
|
},
|
|
55
|
-
|
|
55
|
+
handleBackBoundary: (_f = options.handleBackBoundary) != null ? _f : () => {
|
|
56
|
+
},
|
|
57
|
+
handleLayerClose: (_g = options.handleLayerClose) != null ? _g : () => {
|
|
56
58
|
}
|
|
57
59
|
});
|
|
58
60
|
}
|
package/dist/route-task.mjs
CHANGED
|
@@ -6,8 +6,14 @@ import {
|
|
|
6
6
|
RouteTaskController,
|
|
7
7
|
createRouteTask
|
|
8
8
|
} from "./route-task.mjs";
|
|
9
|
+
import {
|
|
10
|
+
getSavedScrollPosition,
|
|
11
|
+
saveScrollPosition,
|
|
12
|
+
scrollToPosition
|
|
13
|
+
} from "./scroll.mjs";
|
|
9
14
|
import { RouteType } from "./types.mjs";
|
|
10
15
|
import {
|
|
16
|
+
isBrowser,
|
|
11
17
|
isRouteMatched,
|
|
12
18
|
isUrlEqual,
|
|
13
19
|
isValidConfirmHookResult,
|
|
@@ -106,6 +112,44 @@ export const ROUTE_TRANSITION_HOOKS = {
|
|
|
106
112
|
return result;
|
|
107
113
|
}
|
|
108
114
|
}
|
|
115
|
+
if (isBrowser && "scrollRestoration" in window.history)
|
|
116
|
+
window.history.scrollRestoration = "manual";
|
|
117
|
+
if (from && isBrowser && !router.isLayer)
|
|
118
|
+
switch (to.type) {
|
|
119
|
+
case RouteType.push:
|
|
120
|
+
case RouteType.replace: {
|
|
121
|
+
if (!to.keepScrollPosition) {
|
|
122
|
+
saveScrollPosition(from.url.href);
|
|
123
|
+
scrollToPosition({ left: 0, top: 0 });
|
|
124
|
+
} else {
|
|
125
|
+
to.applyNavigationState({
|
|
126
|
+
__keepScrollPosition: to.keepScrollPosition
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
case RouteType.go:
|
|
132
|
+
case RouteType.forward:
|
|
133
|
+
case RouteType.back:
|
|
134
|
+
// for popstate
|
|
135
|
+
case RouteType.unknown: {
|
|
136
|
+
saveScrollPosition(from.url.href);
|
|
137
|
+
setTimeout(async () => {
|
|
138
|
+
const state = window.history.state;
|
|
139
|
+
if (state == null ? void 0 : state.__keepScrollPosition) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const savedPosition = getSavedScrollPosition(
|
|
143
|
+
to.url.href,
|
|
144
|
+
{ left: 0, top: 0 }
|
|
145
|
+
);
|
|
146
|
+
if (!savedPosition) return;
|
|
147
|
+
await router.parsedOptions.nextTick();
|
|
148
|
+
scrollToPosition(savedPosition);
|
|
149
|
+
});
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
109
153
|
switch (to.type) {
|
|
110
154
|
case RouteType.push:
|
|
111
155
|
return ROUTE_TYPE_HANDLERS.push;
|
package/dist/scroll.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Internal {@link ScrollToOptions | `ScrollToOptions`}: `left` and `top` properties always have values */
|
|
2
|
+
interface _ScrollPosition extends ScrollToOptions {
|
|
3
|
+
left: number;
|
|
4
|
+
top: number;
|
|
5
|
+
}
|
|
6
|
+
export interface ScrollPositionElement extends ScrollToOptions {
|
|
7
|
+
/**
|
|
8
|
+
* A valid CSS selector. Some special characters need to be escaped (https://mathiasbynens.be/notes/css-escapes).
|
|
9
|
+
* @example
|
|
10
|
+
* Here are some examples:
|
|
11
|
+
*
|
|
12
|
+
* - `.title`
|
|
13
|
+
* - `.content:first-child`
|
|
14
|
+
* - `#marker`
|
|
15
|
+
* - `#marker\~with\~symbols`
|
|
16
|
+
* - `#marker.with.dot`: Selects `class="with dot" id="marker"`, not `id="marker.with.dot"`
|
|
17
|
+
*
|
|
18
|
+
*/
|
|
19
|
+
el: string | Element;
|
|
20
|
+
}
|
|
21
|
+
/** Scroll parameters */
|
|
22
|
+
export type ScrollPosition = ScrollToOptions | ScrollPositionElement;
|
|
23
|
+
/** Get current window scroll position */
|
|
24
|
+
export declare const winScrollPos: () => _ScrollPosition;
|
|
25
|
+
/** Scroll to specified position */
|
|
26
|
+
export declare function scrollToPosition(position: ScrollPosition): void;
|
|
27
|
+
/** Stored scroll positions */
|
|
28
|
+
export declare const scrollPositions: Map<string, _ScrollPosition>;
|
|
29
|
+
/** Save scroll position */
|
|
30
|
+
export declare function saveScrollPosition(key: string, scrollPosition?: _ScrollPosition): void;
|
|
31
|
+
/** Get saved scroll position */
|
|
32
|
+
export declare function getSavedScrollPosition(key: string, defaultValue?: _ScrollPosition | null): _ScrollPosition | null;
|
|
33
|
+
export {};
|
package/dist/scroll.mjs
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export const winScrollPos = () => ({
|
|
2
|
+
left: window.scrollX,
|
|
3
|
+
top: window.scrollY
|
|
4
|
+
});
|
|
5
|
+
function getElementPosition(el, offset) {
|
|
6
|
+
const docRect = document.documentElement.getBoundingClientRect();
|
|
7
|
+
const elRect = el.getBoundingClientRect();
|
|
8
|
+
return {
|
|
9
|
+
behavior: offset.behavior,
|
|
10
|
+
left: elRect.left - docRect.left - (offset.left || 0),
|
|
11
|
+
top: elRect.top - docRect.top - (offset.top || 0)
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function scrollToPosition(position) {
|
|
15
|
+
if ("el" in position) {
|
|
16
|
+
const positionEl = position.el;
|
|
17
|
+
const el = typeof positionEl === "string" ? document.querySelector(positionEl) : positionEl;
|
|
18
|
+
if (!el) return;
|
|
19
|
+
position = getElementPosition(el, position);
|
|
20
|
+
}
|
|
21
|
+
if ("scrollBehavior" in document.documentElement.style) {
|
|
22
|
+
window.scrollTo(position);
|
|
23
|
+
} else {
|
|
24
|
+
window.scrollTo(
|
|
25
|
+
Number.isFinite(position.left) ? position.left : window.scrollX,
|
|
26
|
+
Number.isFinite(position.top) ? position.top : window.scrollY
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export const scrollPositions = /* @__PURE__ */ new Map();
|
|
31
|
+
const POSITION_KEY = "__scroll_position_key";
|
|
32
|
+
export function saveScrollPosition(key, scrollPosition = winScrollPos()) {
|
|
33
|
+
scrollPosition = { ...scrollPosition };
|
|
34
|
+
scrollPositions.set(key, scrollPosition);
|
|
35
|
+
try {
|
|
36
|
+
if (location.href !== key) return;
|
|
37
|
+
const stateCopy = {
|
|
38
|
+
...history.state || {},
|
|
39
|
+
[POSITION_KEY]: scrollPosition
|
|
40
|
+
};
|
|
41
|
+
history.replaceState(stateCopy, "");
|
|
42
|
+
} catch (error) {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function getSavedScrollPosition(key, defaultValue = null) {
|
|
46
|
+
const scroll = scrollPositions.get(key) || history.state[POSITION_KEY];
|
|
47
|
+
scrollPositions.delete(key);
|
|
48
|
+
return scroll || defaultValue;
|
|
49
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -43,6 +43,7 @@ export interface RouteLocation {
|
|
|
43
43
|
queryArray?: Record<string, string[] | undefined>;
|
|
44
44
|
hash?: string;
|
|
45
45
|
state?: RouteState;
|
|
46
|
+
/** When `true`, maintains current scroll position after navigation (default behavior scrolls to top) */
|
|
46
47
|
keepScrollPosition?: boolean;
|
|
47
48
|
statusCode?: number | null;
|
|
48
49
|
layer?: RouteLayerOptions | null;
|
|
@@ -194,6 +195,7 @@ export interface RouterOptions {
|
|
|
194
195
|
apps?: RouterMicroApp;
|
|
195
196
|
normalizeURL?: (to: URL, from: URL | null) => URL;
|
|
196
197
|
fallback?: RouteHandleHook;
|
|
198
|
+
nextTick?: () => Awaitable<void>;
|
|
197
199
|
rootStyle?: Partial<CSSStyleDeclaration> | false | null;
|
|
198
200
|
layer?: boolean;
|
|
199
201
|
zIndex?: number;
|
package/package.json
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"unbuild": "3.6.0",
|
|
40
40
|
"vitest": "3.2.4"
|
|
41
41
|
},
|
|
42
|
-
"version": "3.0.0-rc.
|
|
42
|
+
"version": "3.0.0-rc.60",
|
|
43
43
|
"type": "module",
|
|
44
44
|
"private": false,
|
|
45
45
|
"exports": {
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"template",
|
|
59
59
|
"public"
|
|
60
60
|
],
|
|
61
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "615e91c617e0a58796c591643c6a2e1d2a1f0a76"
|
|
62
62
|
}
|
package/src/options.ts
CHANGED
|
@@ -83,6 +83,7 @@ export function parsedOptions(
|
|
|
83
83
|
matcher: createMatcher(routes, compiledRoutes),
|
|
84
84
|
normalizeURL: options.normalizeURL ?? ((url) => url),
|
|
85
85
|
fallback: options.fallback ?? fallback,
|
|
86
|
+
nextTick: options.nextTick ?? (() => {}),
|
|
86
87
|
handleBackBoundary: options.handleBackBoundary ?? (() => {}),
|
|
87
88
|
handleLayerClose: options.handleLayerClose ?? (() => {})
|
|
88
89
|
});
|
package/src/route-task.ts
CHANGED
package/src/route-transition.ts
CHANGED
|
@@ -5,6 +5,12 @@ import {
|
|
|
5
5
|
createRouteTask
|
|
6
6
|
} from './route-task';
|
|
7
7
|
import type { Router } from './router';
|
|
8
|
+
import {
|
|
9
|
+
getSavedScrollPosition,
|
|
10
|
+
saveScrollPosition,
|
|
11
|
+
scrollToPosition,
|
|
12
|
+
winScrollPos
|
|
13
|
+
} from './scroll';
|
|
8
14
|
import { RouteType } from './types';
|
|
9
15
|
|
|
10
16
|
import type {
|
|
@@ -15,6 +21,7 @@ import type {
|
|
|
15
21
|
RouteNotifyHook
|
|
16
22
|
} from './types';
|
|
17
23
|
import {
|
|
24
|
+
isBrowser,
|
|
18
25
|
isRouteMatched,
|
|
19
26
|
isUrlEqual,
|
|
20
27
|
isValidConfirmHookResult,
|
|
@@ -174,6 +181,46 @@ export const ROUTE_TRANSITION_HOOKS = {
|
|
|
174
181
|
}
|
|
175
182
|
}
|
|
176
183
|
|
|
184
|
+
if (isBrowser && 'scrollRestoration' in window.history)
|
|
185
|
+
window.history.scrollRestoration = 'manual';
|
|
186
|
+
// handle scroll position
|
|
187
|
+
if (from && isBrowser && !router.isLayer)
|
|
188
|
+
switch (to.type) {
|
|
189
|
+
case RouteType.push:
|
|
190
|
+
case RouteType.replace: {
|
|
191
|
+
if (!to.keepScrollPosition) {
|
|
192
|
+
saveScrollPosition(from.url.href);
|
|
193
|
+
scrollToPosition({ left: 0, top: 0 });
|
|
194
|
+
} else {
|
|
195
|
+
to.applyNavigationState({
|
|
196
|
+
__keepScrollPosition: to.keepScrollPosition
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
case RouteType.go:
|
|
202
|
+
case RouteType.forward:
|
|
203
|
+
case RouteType.back:
|
|
204
|
+
// for popstate
|
|
205
|
+
case RouteType.unknown: {
|
|
206
|
+
saveScrollPosition(from.url.href);
|
|
207
|
+
setTimeout(async () => {
|
|
208
|
+
const state = window.history.state;
|
|
209
|
+
if (state?.__keepScrollPosition) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const savedPosition = getSavedScrollPosition(
|
|
213
|
+
to.url.href,
|
|
214
|
+
{ left: 0, top: 0 }
|
|
215
|
+
);
|
|
216
|
+
if (!savedPosition) return;
|
|
217
|
+
await router.parsedOptions.nextTick();
|
|
218
|
+
scrollToPosition(savedPosition);
|
|
219
|
+
});
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
177
224
|
switch (to.type) {
|
|
178
225
|
case RouteType.push:
|
|
179
226
|
return ROUTE_TYPE_HANDLERS.push;
|
package/src/scroll.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { isBrowser } from './util';
|
|
2
|
+
|
|
3
|
+
/** Internal {@link ScrollToOptions | `ScrollToOptions`}: `left` and `top` properties always have values */
|
|
4
|
+
interface _ScrollPosition extends ScrollToOptions {
|
|
5
|
+
left: number;
|
|
6
|
+
top: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ScrollPositionElement extends ScrollToOptions {
|
|
10
|
+
/**
|
|
11
|
+
* A valid CSS selector. Some special characters need to be escaped (https://mathiasbynens.be/notes/css-escapes).
|
|
12
|
+
* @example
|
|
13
|
+
* Here are some examples:
|
|
14
|
+
*
|
|
15
|
+
* - `.title`
|
|
16
|
+
* - `.content:first-child`
|
|
17
|
+
* - `#marker`
|
|
18
|
+
* - `#marker\~with\~symbols`
|
|
19
|
+
* - `#marker.with.dot`: Selects `class="with dot" id="marker"`, not `id="marker.with.dot"`
|
|
20
|
+
*
|
|
21
|
+
*/
|
|
22
|
+
el: string | Element;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Scroll parameters */
|
|
26
|
+
export type ScrollPosition = ScrollToOptions | ScrollPositionElement;
|
|
27
|
+
|
|
28
|
+
/** Get current window scroll position */
|
|
29
|
+
export const winScrollPos = (): _ScrollPosition => ({
|
|
30
|
+
left: window.scrollX,
|
|
31
|
+
top: window.scrollY
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/** Get element position for scrolling in document */
|
|
35
|
+
function getElementPosition(
|
|
36
|
+
el: Element,
|
|
37
|
+
offset: ScrollToOptions
|
|
38
|
+
): _ScrollPosition {
|
|
39
|
+
const docRect = document.documentElement.getBoundingClientRect();
|
|
40
|
+
const elRect = el.getBoundingClientRect();
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
behavior: offset.behavior,
|
|
44
|
+
left: elRect.left - docRect.left - (offset.left || 0),
|
|
45
|
+
top: elRect.top - docRect.top - (offset.top || 0)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Scroll to specified position */
|
|
50
|
+
export function scrollToPosition(position: ScrollPosition): void {
|
|
51
|
+
if ('el' in position) {
|
|
52
|
+
const positionEl = position.el;
|
|
53
|
+
|
|
54
|
+
const el =
|
|
55
|
+
typeof positionEl === 'string'
|
|
56
|
+
? document.querySelector(positionEl)
|
|
57
|
+
: positionEl;
|
|
58
|
+
|
|
59
|
+
if (!el) return;
|
|
60
|
+
|
|
61
|
+
position = getElementPosition(el, position);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if ('scrollBehavior' in document.documentElement.style) {
|
|
65
|
+
window.scrollTo(position);
|
|
66
|
+
} else {
|
|
67
|
+
window.scrollTo(
|
|
68
|
+
Number.isFinite(position.left) ? position.left! : window.scrollX,
|
|
69
|
+
Number.isFinite(position.top) ? position.top! : window.scrollY
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Stored scroll positions */
|
|
75
|
+
export const scrollPositions = new Map<string, _ScrollPosition>();
|
|
76
|
+
|
|
77
|
+
const POSITION_KEY = '__scroll_position_key';
|
|
78
|
+
|
|
79
|
+
/** Save scroll position */
|
|
80
|
+
export function saveScrollPosition(
|
|
81
|
+
key: string,
|
|
82
|
+
scrollPosition = winScrollPos()
|
|
83
|
+
) {
|
|
84
|
+
scrollPosition = { ...scrollPosition };
|
|
85
|
+
scrollPositions.set(key, scrollPosition);
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
if (location.href !== key) return;
|
|
89
|
+
// preserve the existing history state as it could be overridden by the user
|
|
90
|
+
const stateCopy = {
|
|
91
|
+
...(history.state || {}),
|
|
92
|
+
[POSITION_KEY]: scrollPosition
|
|
93
|
+
};
|
|
94
|
+
history.replaceState(stateCopy, '');
|
|
95
|
+
} catch (error) {}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Get saved scroll position */
|
|
99
|
+
export function getSavedScrollPosition(
|
|
100
|
+
key: string,
|
|
101
|
+
defaultValue: _ScrollPosition | null = null
|
|
102
|
+
): _ScrollPosition | null {
|
|
103
|
+
const scroll = scrollPositions.get(key) || history.state[POSITION_KEY];
|
|
104
|
+
|
|
105
|
+
// Saved scroll position should not be used multiple times, next time should use newly saved position
|
|
106
|
+
scrollPositions.delete(key);
|
|
107
|
+
return scroll || defaultValue;
|
|
108
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -91,6 +91,7 @@ export interface RouteLocation {
|
|
|
91
91
|
queryArray?: Record<string, string[] | undefined>;
|
|
92
92
|
hash?: string;
|
|
93
93
|
state?: RouteState;
|
|
94
|
+
/** When `true`, maintains current scroll position after navigation (default behavior scrolls to top) */
|
|
94
95
|
keepScrollPosition?: boolean;
|
|
95
96
|
statusCode?: number | null;
|
|
96
97
|
layer?: RouteLayerOptions | null;
|
|
@@ -266,6 +267,7 @@ export interface RouterOptions {
|
|
|
266
267
|
apps?: RouterMicroApp;
|
|
267
268
|
normalizeURL?: (to: URL, from: URL | null) => URL;
|
|
268
269
|
fallback?: RouteHandleHook;
|
|
270
|
+
nextTick?: () => Awaitable<void>;
|
|
269
271
|
|
|
270
272
|
rootStyle?: Partial<CSSStyleDeclaration> | false | null;
|
|
271
273
|
layer?: boolean;
|