space-router 1.1.0 → 1.2.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/dist/history.d.ts +5 -0
- package/dist/history.js +28 -17
- package/dist/index.d.ts +1 -1
- package/dist/router.d.ts +2 -1
- package/dist/router.js +1 -2
- package/package.json +1 -1
- package/src/history.ts +36 -15
- package/src/index.ts +1 -1
- package/src/router.ts +3 -3
package/dist/history.d.ts
CHANGED
|
@@ -5,8 +5,13 @@ export interface History {
|
|
|
5
5
|
push(url: string): void;
|
|
6
6
|
replace(url: string): void;
|
|
7
7
|
}
|
|
8
|
+
export interface ScheduleInfo {
|
|
9
|
+
traversal: boolean;
|
|
10
|
+
}
|
|
11
|
+
export type Schedule = (fire: () => void, info: ScheduleInfo) => void;
|
|
8
12
|
export interface CreateHistoryOptions {
|
|
9
13
|
mode?: Mode;
|
|
10
14
|
sync?: boolean;
|
|
15
|
+
schedule?: Schedule;
|
|
11
16
|
}
|
|
12
17
|
export declare function createHistory(options?: CreateHistoryOptions): History;
|
package/dist/history.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export function createHistory(options = {}) {
|
|
2
|
-
const
|
|
2
|
+
const schedule = options.schedule || (options.sync ? (fire) => fire() : (fire) => queueMicrotask(fire));
|
|
3
3
|
let mode = options.mode || 'history';
|
|
4
4
|
let listener = null;
|
|
5
|
-
let
|
|
5
|
+
let seq = 0;
|
|
6
|
+
let selfNavs = 0;
|
|
6
7
|
const memory = [];
|
|
7
8
|
if (typeof window === 'undefined') {
|
|
8
9
|
mode = 'memory';
|
|
@@ -11,16 +12,24 @@ export function createHistory(options = {}) {
|
|
|
11
12
|
if (listener)
|
|
12
13
|
listener(getUrl());
|
|
13
14
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
});
|
|
15
|
+
// each scheduled fire captures its seq: superseded fires no-op, and the
|
|
16
|
+
// surviving one reads the url fresh in emit() — so racing schedules of any
|
|
17
|
+
// mix (e.g. a deferred traversal emit overtaken by a push's microtask emit)
|
|
18
|
+
// coalesce into a single emit of the final url
|
|
19
|
+
function scheduleEmit(traversal) {
|
|
20
|
+
const s = ++seq;
|
|
21
|
+
schedule(() => {
|
|
22
|
+
if (s === seq)
|
|
23
|
+
emit();
|
|
24
|
+
}, { traversal });
|
|
25
|
+
}
|
|
26
|
+
function onTraversal() {
|
|
27
|
+
// a hashchange caused by our own push/replace is a navigation,
|
|
28
|
+
// not a back/forward traversal
|
|
29
|
+
const traversal = selfNavs === 0;
|
|
30
|
+
if (selfNavs > 0)
|
|
31
|
+
selfNavs--;
|
|
32
|
+
scheduleEmit(traversal);
|
|
24
33
|
}
|
|
25
34
|
function listen(onChange) {
|
|
26
35
|
if (listener)
|
|
@@ -28,8 +37,8 @@ export function createHistory(options = {}) {
|
|
|
28
37
|
listener = onChange;
|
|
29
38
|
let off;
|
|
30
39
|
if (mode !== 'memory') {
|
|
31
|
-
off = on(window, mode === 'history' ? 'popstate' : 'hashchange',
|
|
32
|
-
|
|
40
|
+
off = on(window, mode === 'history' ? 'popstate' : 'hashchange', onTraversal);
|
|
41
|
+
scheduleEmit(false);
|
|
33
42
|
}
|
|
34
43
|
return () => {
|
|
35
44
|
listener = null;
|
|
@@ -41,16 +50,18 @@ export function createHistory(options = {}) {
|
|
|
41
50
|
url = url.replace(/^\/?#?\/?/, '/').replace(/\/$/, '') || '/';
|
|
42
51
|
if (mode === 'history') {
|
|
43
52
|
history[replace ? 'replaceState' : 'pushState']({}, '', url);
|
|
44
|
-
|
|
53
|
+
scheduleEmit(false);
|
|
45
54
|
}
|
|
46
55
|
else if (mode === 'hash') {
|
|
47
56
|
// hashchange only fires when the URL actually changes; if it doesn't,
|
|
48
57
|
// we schedule the emit manually so navigation stays consistent with
|
|
49
58
|
// history mode (where pushState is silent and we always schedule).
|
|
50
59
|
const same = url === getUrl();
|
|
60
|
+
if (!same)
|
|
61
|
+
selfNavs++;
|
|
51
62
|
location[replace ? 'replace' : 'assign']('#' + url);
|
|
52
63
|
if (same)
|
|
53
|
-
|
|
64
|
+
scheduleEmit(false);
|
|
54
65
|
}
|
|
55
66
|
else if (mode === 'memory') {
|
|
56
67
|
if (replace && memory.length) {
|
|
@@ -59,7 +70,7 @@ export function createHistory(options = {}) {
|
|
|
59
70
|
else {
|
|
60
71
|
memory.push(url);
|
|
61
72
|
}
|
|
62
|
-
|
|
73
|
+
scheduleEmit(false);
|
|
63
74
|
}
|
|
64
75
|
}
|
|
65
76
|
function getUrl() {
|
package/dist/index.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ export { qs } from './qs.ts';
|
|
|
2
2
|
export { createHistory } from './history.ts';
|
|
3
3
|
export { createMatcher, createRouter, merge } from './router.ts';
|
|
4
4
|
export type { Qs } from './qs.ts';
|
|
5
|
-
export type { Mode, History, CreateHistoryOptions } from './history.ts';
|
|
5
|
+
export type { Mode, History, CreateHistoryOptions, Schedule, ScheduleInfo } from './history.ts';
|
|
6
6
|
export type { MatcherOptions, RouteData, RouterOptions, Route, NavigateTarget, To, Redirect, RouteDefinition, Matcher, Router, } from './router.ts';
|
package/dist/router.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { type MatchedRoute } from './match.ts';
|
|
2
|
-
import { type Mode } from './history.ts';
|
|
2
|
+
import { type Mode, type Schedule } from './history.ts';
|
|
3
3
|
import { type Qs } from './qs.ts';
|
|
4
4
|
export interface RouterOptions {
|
|
5
5
|
mode?: Mode;
|
|
6
6
|
qs?: Qs;
|
|
7
7
|
sync?: boolean;
|
|
8
|
+
schedule?: Schedule;
|
|
8
9
|
}
|
|
9
10
|
export interface MatcherOptions {
|
|
10
11
|
qs?: Qs;
|
package/dist/router.js
CHANGED
|
@@ -6,8 +6,7 @@ const MAX_REDIRECTS = 10;
|
|
|
6
6
|
export function createRouter(options = {}) {
|
|
7
7
|
const mode = options.mode || 'history';
|
|
8
8
|
const qs = options.qs || defaultQs;
|
|
9
|
-
const
|
|
10
|
-
const history = createHistory({ mode, sync });
|
|
9
|
+
const history = createHistory({ mode, sync: options.sync, schedule: options.schedule });
|
|
11
10
|
let matcher = createMatcher([], { qs });
|
|
12
11
|
const router = {
|
|
13
12
|
listen(routeMap, cb) {
|
package/package.json
CHANGED
package/src/history.ts
CHANGED
|
@@ -7,16 +7,24 @@ export interface History {
|
|
|
7
7
|
replace(url: string): void
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
export interface ScheduleInfo {
|
|
11
|
+
traversal: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type Schedule = (fire: () => void, info: ScheduleInfo) => void
|
|
15
|
+
|
|
10
16
|
export interface CreateHistoryOptions {
|
|
11
17
|
mode?: Mode
|
|
12
18
|
sync?: boolean
|
|
19
|
+
schedule?: Schedule
|
|
13
20
|
}
|
|
14
21
|
|
|
15
22
|
export function createHistory(options: CreateHistoryOptions = {}): History {
|
|
16
|
-
const
|
|
23
|
+
const schedule: Schedule = options.schedule || (options.sync ? (fire) => fire() : (fire) => queueMicrotask(fire))
|
|
17
24
|
let mode: Mode = options.mode || 'history'
|
|
18
25
|
let listener: ((url: string) => void) | null = null
|
|
19
|
-
let
|
|
26
|
+
let seq = 0
|
|
27
|
+
let selfNavs = 0
|
|
20
28
|
|
|
21
29
|
const memory: string[] = []
|
|
22
30
|
|
|
@@ -28,14 +36,26 @@ export function createHistory(options: CreateHistoryOptions = {}): History {
|
|
|
28
36
|
if (listener) listener(getUrl())
|
|
29
37
|
}
|
|
30
38
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
// each scheduled fire captures its seq: superseded fires no-op, and the
|
|
40
|
+
// surviving one reads the url fresh in emit() — so racing schedules of any
|
|
41
|
+
// mix (e.g. a deferred traversal emit overtaken by a push's microtask emit)
|
|
42
|
+
// coalesce into a single emit of the final url
|
|
43
|
+
function scheduleEmit(traversal: boolean) {
|
|
44
|
+
const s = ++seq
|
|
45
|
+
schedule(
|
|
46
|
+
() => {
|
|
47
|
+
if (s === seq) emit()
|
|
48
|
+
},
|
|
49
|
+
{ traversal },
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function onTraversal() {
|
|
54
|
+
// a hashchange caused by our own push/replace is a navigation,
|
|
55
|
+
// not a back/forward traversal
|
|
56
|
+
const traversal = selfNavs === 0
|
|
57
|
+
if (selfNavs > 0) selfNavs--
|
|
58
|
+
scheduleEmit(traversal)
|
|
39
59
|
}
|
|
40
60
|
|
|
41
61
|
function listen(onChange: (url: string) => void) {
|
|
@@ -43,8 +63,8 @@ export function createHistory(options: CreateHistoryOptions = {}): History {
|
|
|
43
63
|
listener = onChange
|
|
44
64
|
let off: (() => void) | undefined
|
|
45
65
|
if (mode !== 'memory') {
|
|
46
|
-
off = on(window, mode === 'history' ? 'popstate' : 'hashchange',
|
|
47
|
-
|
|
66
|
+
off = on(window, mode === 'history' ? 'popstate' : 'hashchange', onTraversal)
|
|
67
|
+
scheduleEmit(false)
|
|
48
68
|
}
|
|
49
69
|
return () => {
|
|
50
70
|
listener = null
|
|
@@ -56,21 +76,22 @@ export function createHistory(options: CreateHistoryOptions = {}): History {
|
|
|
56
76
|
url = url.replace(/^\/?#?\/?/, '/').replace(/\/$/, '') || '/'
|
|
57
77
|
if (mode === 'history') {
|
|
58
78
|
history[replace ? 'replaceState' : 'pushState']({}, '', url)
|
|
59
|
-
|
|
79
|
+
scheduleEmit(false)
|
|
60
80
|
} else if (mode === 'hash') {
|
|
61
81
|
// hashchange only fires when the URL actually changes; if it doesn't,
|
|
62
82
|
// we schedule the emit manually so navigation stays consistent with
|
|
63
83
|
// history mode (where pushState is silent and we always schedule).
|
|
64
84
|
const same = url === getUrl()
|
|
85
|
+
if (!same) selfNavs++
|
|
65
86
|
location[replace ? 'replace' : 'assign']('#' + url)
|
|
66
|
-
if (same)
|
|
87
|
+
if (same) scheduleEmit(false)
|
|
67
88
|
} else if (mode === 'memory') {
|
|
68
89
|
if (replace && memory.length) {
|
|
69
90
|
memory[memory.length - 1] = url
|
|
70
91
|
} else {
|
|
71
92
|
memory.push(url)
|
|
72
93
|
}
|
|
73
|
-
|
|
94
|
+
scheduleEmit(false)
|
|
74
95
|
}
|
|
75
96
|
}
|
|
76
97
|
|
package/src/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { createHistory } from './history.ts'
|
|
|
3
3
|
export { createMatcher, createRouter, merge } from './router.ts'
|
|
4
4
|
|
|
5
5
|
export type { Qs } from './qs.ts'
|
|
6
|
-
export type { Mode, History, CreateHistoryOptions } from './history.ts'
|
|
6
|
+
export type { Mode, History, CreateHistoryOptions, Schedule, ScheduleInfo } from './history.ts'
|
|
7
7
|
export type {
|
|
8
8
|
MatcherOptions,
|
|
9
9
|
RouteData,
|
package/src/router.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { matchOne, type MatchedRoute } from './match.ts'
|
|
2
|
-
import { createHistory, type Mode } from './history.ts'
|
|
2
|
+
import { createHistory, type Mode, type Schedule } from './history.ts'
|
|
3
3
|
import { qs as defaultQs, type Qs } from './qs.ts'
|
|
4
4
|
|
|
5
5
|
export interface RouterOptions {
|
|
6
6
|
mode?: Mode
|
|
7
7
|
qs?: Qs
|
|
8
8
|
sync?: boolean
|
|
9
|
+
schedule?: Schedule
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export interface MatcherOptions {
|
|
@@ -64,8 +65,7 @@ const MAX_REDIRECTS = 10
|
|
|
64
65
|
export function createRouter<Data = Record<string, unknown>>(options: RouterOptions = {}): Router<Data> {
|
|
65
66
|
const mode = options.mode || 'history'
|
|
66
67
|
const qs = options.qs || defaultQs
|
|
67
|
-
const
|
|
68
|
-
const history = createHistory({ mode, sync })
|
|
68
|
+
const history = createHistory({ mode, sync: options.sync, schedule: options.schedule })
|
|
69
69
|
|
|
70
70
|
let matcher = createMatcher<Data>([], { qs })
|
|
71
71
|
|