@typed/navigation 0.5.3 → 0.6.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 +5 -0
- package/dist/cjs/Blocking.js +53 -0
- package/dist/cjs/Blocking.js.map +1 -0
- package/dist/cjs/Layer.js +27 -0
- package/dist/cjs/Layer.js.map +1 -0
- package/dist/cjs/Navigation.js +184 -62
- package/dist/cjs/Navigation.js.map +1 -1
- package/dist/cjs/index.js +36 -17
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/internal/fromWindow.js +376 -0
- package/dist/cjs/internal/fromWindow.js.map +1 -0
- package/dist/cjs/internal/memory.js +67 -0
- package/dist/cjs/internal/memory.js.map +1 -0
- package/dist/cjs/internal/shared.js +403 -0
- package/dist/cjs/internal/shared.js.map +1 -0
- package/dist/dts/Blocking.d.ts +33 -0
- package/dist/dts/Blocking.d.ts.map +1 -0
- package/dist/dts/Layer.d.ts +44 -0
- package/dist/dts/Layer.d.ts.map +1 -0
- package/dist/dts/Navigation.d.ts +377 -0
- package/dist/dts/Navigation.d.ts.map +1 -0
- package/dist/dts/index.d.ts +17 -0
- package/dist/dts/index.d.ts.map +1 -0
- package/dist/dts/internal/fromWindow.d.ts +12 -0
- package/dist/dts/internal/fromWindow.d.ts.map +1 -0
- package/dist/dts/internal/memory.d.ts +6 -0
- package/dist/dts/internal/memory.d.ts.map +1 -0
- package/dist/dts/internal/shared.d.ts +139 -0
- package/dist/dts/internal/shared.d.ts.map +1 -0
- package/dist/esm/Blocking.js +39 -0
- package/dist/esm/Blocking.js.map +1 -0
- package/dist/esm/Layer.js +18 -0
- package/dist/esm/Layer.js.map +1 -0
- package/dist/esm/Navigation.js +166 -0
- package/dist/esm/Navigation.js.map +1 -0
- package/dist/esm/index.js +17 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/fromWindow.js +273 -0
- package/dist/esm/internal/fromWindow.js.map +1 -0
- package/dist/esm/internal/memory.js +54 -0
- package/dist/esm/internal/memory.js.map +1 -0
- package/dist/esm/internal/shared.js +336 -0
- package/dist/esm/internal/shared.js.map +1 -0
- package/dist/esm/package.json +4 -0
- package/package.json +52 -32
- package/src/Blocking.ts +102 -0
- package/src/Layer.ts +53 -0
- package/src/Navigation.ts +342 -159
- package/src/index.ts +17 -3
- package/src/internal/fromWindow.ts +421 -0
- package/src/internal/memory.ts +79 -0
- package/src/internal/shared.ts +514 -0
- package/dist/DOM.d.ts +0 -12
- package/dist/DOM.d.ts.map +0 -1
- package/dist/DOM.js +0 -87
- package/dist/DOM.js.map +0 -1
- package/dist/Memory.d.ts +0 -10
- package/dist/Memory.d.ts.map +0 -1
- package/dist/Memory.js +0 -55
- package/dist/Memory.js.map +0 -1
- package/dist/Navigation.d.ts +0 -116
- package/dist/Navigation.d.ts.map +0 -1
- package/dist/Navigation.js +0 -36
- package/dist/Navigation.js.map +0 -1
- package/dist/_makeServerWindow.d.ts +0 -16
- package/dist/_makeServerWindow.d.ts.map +0 -1
- package/dist/_makeServerWindow.js +0 -9
- package/dist/_makeServerWindow.js.map +0 -1
- package/dist/cjs/DOM.d.ts +0 -12
- package/dist/cjs/DOM.d.ts.map +0 -1
- package/dist/cjs/DOM.js +0 -116
- package/dist/cjs/DOM.js.map +0 -1
- package/dist/cjs/Memory.d.ts +0 -10
- package/dist/cjs/Memory.d.ts.map +0 -1
- package/dist/cjs/Memory.js +0 -82
- package/dist/cjs/Memory.js.map +0 -1
- package/dist/cjs/Navigation.d.ts +0 -116
- package/dist/cjs/Navigation.d.ts.map +0 -1
- package/dist/cjs/_makeServerWindow.d.ts +0 -16
- package/dist/cjs/_makeServerWindow.d.ts.map +0 -1
- package/dist/cjs/_makeServerWindow.js +0 -36
- package/dist/cjs/_makeServerWindow.js.map +0 -1
- package/dist/cjs/constant.d.ts +0 -2
- package/dist/cjs/constant.d.ts.map +0 -1
- package/dist/cjs/constant.js +0 -30
- package/dist/cjs/constant.js.map +0 -1
- package/dist/cjs/dom-intent.d.ts +0 -28
- package/dist/cjs/dom-intent.d.ts.map +0 -1
- package/dist/cjs/dom-intent.js +0 -172
- package/dist/cjs/dom-intent.js.map +0 -1
- package/dist/cjs/history.d.ts +0 -31
- package/dist/cjs/history.d.ts.map +0 -1
- package/dist/cjs/history.js +0 -131
- package/dist/cjs/history.js.map +0 -1
- package/dist/cjs/index.d.ts +0 -4
- package/dist/cjs/index.d.ts.map +0 -1
- package/dist/cjs/json.d.ts +0 -13
- package/dist/cjs/json.d.ts.map +0 -1
- package/dist/cjs/json.js +0 -24
- package/dist/cjs/json.js.map +0 -1
- package/dist/cjs/memory-intent.d.ts +0 -27
- package/dist/cjs/memory-intent.d.ts.map +0 -1
- package/dist/cjs/memory-intent.js +0 -156
- package/dist/cjs/memory-intent.js.map +0 -1
- package/dist/cjs/model.d.ts +0 -22
- package/dist/cjs/model.d.ts.map +0 -1
- package/dist/cjs/model.js +0 -48
- package/dist/cjs/model.js.map +0 -1
- package/dist/cjs/shared-intent.d.ts +0 -14
- package/dist/cjs/shared-intent.d.ts.map +0 -1
- package/dist/cjs/shared-intent.js +0 -82
- package/dist/cjs/shared-intent.js.map +0 -1
- package/dist/cjs/storage.d.ts +0 -19
- package/dist/cjs/storage.d.ts.map +0 -1
- package/dist/cjs/storage.js +0 -101
- package/dist/cjs/storage.js.map +0 -1
- package/dist/cjs/util.d.ts +0 -5
- package/dist/cjs/util.d.ts.map +0 -1
- package/dist/cjs/util.js +0 -39
- package/dist/cjs/util.js.map +0 -1
- package/dist/constant.d.ts +0 -2
- package/dist/constant.d.ts.map +0 -1
- package/dist/constant.js +0 -4
- package/dist/constant.js.map +0 -1
- package/dist/dom-intent.d.ts +0 -28
- package/dist/dom-intent.d.ts.map +0 -1
- package/dist/dom-intent.js +0 -140
- package/dist/dom-intent.js.map +0 -1
- package/dist/history.d.ts +0 -31
- package/dist/history.d.ts.map +0 -1
- package/dist/history.js +0 -104
- package/dist/history.js.map +0 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -4
- package/dist/index.js.map +0 -1
- package/dist/intent.d.ts +0 -31
- package/dist/intent.d.ts.map +0 -1
- package/dist/intent.js +0 -157
- package/dist/intent.js.map +0 -1
- package/dist/json.d.ts +0 -13
- package/dist/json.d.ts.map +0 -1
- package/dist/json.js +0 -17
- package/dist/json.js.map +0 -1
- package/dist/memory-intent.d.ts +0 -27
- package/dist/memory-intent.d.ts.map +0 -1
- package/dist/memory-intent.js +0 -124
- package/dist/memory-intent.js.map +0 -1
- package/dist/model.d.ts +0 -22
- package/dist/model.d.ts.map +0 -1
- package/dist/model.js +0 -21
- package/dist/model.js.map +0 -1
- package/dist/shared-intent.d.ts +0 -14
- package/dist/shared-intent.d.ts.map +0 -1
- package/dist/shared-intent.js +0 -51
- package/dist/shared-intent.js.map +0 -1
- package/dist/storage.d.ts +0 -19
- package/dist/storage.d.ts.map +0 -1
- package/dist/storage.js +0 -73
- package/dist/storage.js.map +0 -1
- package/dist/tsconfig.cjs.build.tsbuildinfo +0 -1
- package/dist/util.d.ts +0 -5
- package/dist/util.d.ts.map +0 -1
- package/dist/util.js +0 -12
- package/dist/util.js.map +0 -1
- package/eslintrc.json +0 -3
- package/project.json +0 -46
- package/src/DOM.test.ts +0 -699
- package/src/DOM.ts +0 -163
- package/src/Memory.test.ts +0 -464
- package/src/Memory.ts +0 -102
- package/src/_makeServerWindow.ts +0 -28
- package/src/dom-intent.ts +0 -268
- package/src/history.ts +0 -165
- package/src/json.ts +0 -31
- package/src/memory-intent.ts +0 -224
- package/src/model.ts +0 -54
- package/src/shared-intent.ts +0 -117
- package/src/storage.ts +0 -101
- package/src/util.ts +0 -20
- package/tsconfig.build.json +0 -4
- package/tsconfig.build.tsbuildinfo +0 -1
- package/tsconfig.cjs.build.json +0 -22
- package/tsconfig.json +0 -27
- package/vite.config.mjs +0 -3
package/src/Memory.ts
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { pipe } from '@effect/data/Function'
|
|
2
|
-
import * as Option from '@effect/data/Option'
|
|
3
|
-
import * as Cause from '@effect/io/Cause'
|
|
4
|
-
import * as Effect from '@effect/io/Effect'
|
|
5
|
-
import * as Layer from '@effect/io/Layer'
|
|
6
|
-
|
|
7
|
-
import type { DomNavigationOptions } from './DOM.js'
|
|
8
|
-
import {
|
|
9
|
-
Destination,
|
|
10
|
-
Navigation,
|
|
11
|
-
NavigationError,
|
|
12
|
-
NavigationEvent,
|
|
13
|
-
NavigationType,
|
|
14
|
-
} from './Navigation.js'
|
|
15
|
-
import { makeIntent } from './memory-intent.js'
|
|
16
|
-
import { Model, makeModel } from './model.js'
|
|
17
|
-
import { createKey } from './util.js'
|
|
18
|
-
|
|
19
|
-
export interface MemoryNavigationOptions extends DomNavigationOptions {
|
|
20
|
-
readonly initialUrl: URL
|
|
21
|
-
readonly initialState?: unknown
|
|
22
|
-
readonly base?: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function memory(options: MemoryNavigationOptions): Layer.Layer<never, never, Navigation> {
|
|
26
|
-
return Navigation.layerScoped(
|
|
27
|
-
Effect.gen(function* ($) {
|
|
28
|
-
const initial: Destination = {
|
|
29
|
-
key: options.initialKey ?? (yield* $(createKey)),
|
|
30
|
-
url: options.initialUrl,
|
|
31
|
-
state: options.initialState,
|
|
32
|
-
}
|
|
33
|
-
const initialEvent: NavigationEvent = {
|
|
34
|
-
destination: initial,
|
|
35
|
-
hashChange: false,
|
|
36
|
-
navigationType: NavigationType.Push,
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const model: Model = yield* $(makeModel([initialEvent], 0))
|
|
40
|
-
const intent = makeIntent(model, options)
|
|
41
|
-
|
|
42
|
-
// Used to ensure ordering of navigation events
|
|
43
|
-
const lock = Effect.unsafeMakeSemaphore(1).withPermits(1)
|
|
44
|
-
|
|
45
|
-
const handleNavigationError =
|
|
46
|
-
(depth: number) =>
|
|
47
|
-
(
|
|
48
|
-
error: NavigationError | Cause.NoSuchElementException,
|
|
49
|
-
): Effect.Effect<never, never, Destination> =>
|
|
50
|
-
Effect.gen(function* ($) {
|
|
51
|
-
if (depth >= 50) {
|
|
52
|
-
throw new Error(
|
|
53
|
-
'Too many redirects. You may have an infinite loop of onNavigation handlers that are redirecting.',
|
|
54
|
-
)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
switch (error._tag) {
|
|
58
|
-
case 'NoSuchElementException':
|
|
59
|
-
case 'CancelNavigation':
|
|
60
|
-
return yield* $(model.currentEntry.get)
|
|
61
|
-
case 'RedirectNavigation':
|
|
62
|
-
return yield* $(
|
|
63
|
-
Effect.catchAll(
|
|
64
|
-
intent.navigate(error.url, error),
|
|
65
|
-
handleNavigationError(depth + 1),
|
|
66
|
-
),
|
|
67
|
-
)
|
|
68
|
-
}
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
const catchNavigationError = <R, A>(
|
|
72
|
-
effect: Effect.Effect<R, NavigationError | Cause.NoSuchElementException, A>,
|
|
73
|
-
) => Effect.catchAll(effect, handleNavigationError(0))
|
|
74
|
-
|
|
75
|
-
// Construct our service
|
|
76
|
-
const navigation: Navigation = {
|
|
77
|
-
back: lock(catchNavigationError(intent.back)),
|
|
78
|
-
base: '/',
|
|
79
|
-
canGoBack: model.canGoBack,
|
|
80
|
-
canGoForward: model.canGoForward,
|
|
81
|
-
currentEntry: model.currentEntry,
|
|
82
|
-
entries: model.entries,
|
|
83
|
-
forward: lock(catchNavigationError(intent.forward)),
|
|
84
|
-
goTo: (n) =>
|
|
85
|
-
pipe(
|
|
86
|
-
n,
|
|
87
|
-
intent.goTo,
|
|
88
|
-
Effect.catchAll((a) => pipe(a, handleNavigationError(0), Effect.map(Option.some))),
|
|
89
|
-
lock,
|
|
90
|
-
),
|
|
91
|
-
navigate: (url, options) => pipe(intent.navigate(url, options), catchNavigationError, lock),
|
|
92
|
-
onNavigation: (handler, options) =>
|
|
93
|
-
pipe(intent.onNavigation(handler, options), catchNavigationError, Effect.asUnit),
|
|
94
|
-
onNavigationEnd: (handler, options) =>
|
|
95
|
-
Effect.asUnit(intent.onNavigationEnd(handler, options)),
|
|
96
|
-
reload: lock(catchNavigationError(intent.reload)),
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return navigation
|
|
100
|
-
}),
|
|
101
|
-
)
|
|
102
|
-
}
|
package/src/_makeServerWindow.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { Window, GlobalThis } from '@typed/dom'
|
|
2
|
-
import * as happyDom from 'happy-dom'
|
|
3
|
-
|
|
4
|
-
export interface ServerWindowOptions {
|
|
5
|
-
readonly url: string
|
|
6
|
-
|
|
7
|
-
readonly innerWidth?: number
|
|
8
|
-
readonly innerHeight?: number
|
|
9
|
-
readonly settings?: {
|
|
10
|
-
readonly disableJavaScriptEvaluation: boolean
|
|
11
|
-
readonly disableJavaScriptFileLoading: boolean
|
|
12
|
-
readonly disableCSSFileLoading: boolean
|
|
13
|
-
readonly enableFileSystemHttpRequests: boolean
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function makeServerWindow(
|
|
18
|
-
options?: ServerWindowOptions,
|
|
19
|
-
): Window & GlobalThis & Pick<InstanceType<typeof happyDom.Window>, 'happyDOM'> {
|
|
20
|
-
const win: Window & GlobalThis & Pick<InstanceType<typeof happyDom.Window>, 'happyDOM'> =
|
|
21
|
-
new happyDom.Window({
|
|
22
|
-
...options,
|
|
23
|
-
}) as any
|
|
24
|
-
|
|
25
|
-
return win
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export const html5Doctype = '<!DOCTYPE html>'
|
package/src/dom-intent.ts
DELETED
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
import { Option } from '@effect/data/Option'
|
|
2
|
-
import * as Effect from '@effect/io/Effect'
|
|
3
|
-
import { Location } from '@typed/dom'
|
|
4
|
-
|
|
5
|
-
import type { DomNavigationOptions } from './DOM.js'
|
|
6
|
-
import {
|
|
7
|
-
Destination,
|
|
8
|
-
NavigateOptions,
|
|
9
|
-
NavigationError,
|
|
10
|
-
NavigationEvent,
|
|
11
|
-
NavigationType,
|
|
12
|
-
} from './Navigation.js'
|
|
13
|
-
import { Model } from './model.js'
|
|
14
|
-
import {
|
|
15
|
-
Notify,
|
|
16
|
-
NotifyEnd,
|
|
17
|
-
Save,
|
|
18
|
-
makeGoTo,
|
|
19
|
-
makeNotify,
|
|
20
|
-
makeNotifyEnd,
|
|
21
|
-
makeOnNavigation,
|
|
22
|
-
makeOnNavigationEnd,
|
|
23
|
-
} from './shared-intent.js'
|
|
24
|
-
import { saveToStorage } from './storage.js'
|
|
25
|
-
import { createKey, getUrl } from './util.js'
|
|
26
|
-
|
|
27
|
-
// Roughly the number of History entries in a browser anyways
|
|
28
|
-
const DEFAULT_MAX_ENTRIES = 50
|
|
29
|
-
|
|
30
|
-
export type DomIntent = {
|
|
31
|
-
readonly back: (skipHistory: boolean) => ReturnType<ReturnType<typeof makeGo>>
|
|
32
|
-
|
|
33
|
-
readonly forward: (skipHistory: boolean) => ReturnType<ReturnType<typeof makeGo>>
|
|
34
|
-
|
|
35
|
-
readonly push: ReturnType<typeof makePush>
|
|
36
|
-
|
|
37
|
-
readonly replace: ReturnType<typeof makeReplace>
|
|
38
|
-
|
|
39
|
-
readonly navigate: (
|
|
40
|
-
url: string,
|
|
41
|
-
options?: NavigateOptions,
|
|
42
|
-
) => ReturnType<ReturnType<typeof makePush | typeof makeReplace>>
|
|
43
|
-
|
|
44
|
-
readonly notify: Notify
|
|
45
|
-
|
|
46
|
-
readonly go: ReturnType<typeof makeGo>
|
|
47
|
-
|
|
48
|
-
readonly goTo: (
|
|
49
|
-
key: string,
|
|
50
|
-
) => Effect.Effect<Storage | History, NavigationError, Option<Destination>>
|
|
51
|
-
|
|
52
|
-
readonly reload: ReturnType<typeof makeReload>
|
|
53
|
-
|
|
54
|
-
readonly onNavigation: ReturnType<typeof makeOnNavigation>
|
|
55
|
-
|
|
56
|
-
readonly onNavigationEnd: ReturnType<typeof makeOnNavigationEnd>
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export const makeIntent = (
|
|
60
|
-
model: Model,
|
|
61
|
-
base: string,
|
|
62
|
-
history: History,
|
|
63
|
-
options: DomNavigationOptions,
|
|
64
|
-
): DomIntent => {
|
|
65
|
-
const maxEntries = Math.abs(options.maxEntries ?? DEFAULT_MAX_ENTRIES)
|
|
66
|
-
const notify = makeNotify(model)
|
|
67
|
-
const notifyEnd = makeNotifyEnd(model)
|
|
68
|
-
const save = makeSave(model)
|
|
69
|
-
const go = makeGo(model, notify, notifyEnd, save, history)
|
|
70
|
-
const replace = makeReplace(model, notify, notifyEnd, save, history, base)
|
|
71
|
-
const push = makePush(model, notify, notifyEnd, save, base, history, maxEntries)
|
|
72
|
-
|
|
73
|
-
return {
|
|
74
|
-
back: (skipHistory: boolean) => go(-1, skipHistory),
|
|
75
|
-
forward: (skipHistory: boolean) => go(1, skipHistory),
|
|
76
|
-
push,
|
|
77
|
-
replace,
|
|
78
|
-
navigate: (url: string, options: NavigateOptions = {}) =>
|
|
79
|
-
options.history === 'replace' ? replace(url, options) : push(url, options),
|
|
80
|
-
notify,
|
|
81
|
-
go: go,
|
|
82
|
-
goTo: makeGoTo(model, go),
|
|
83
|
-
reload: makeReload(model, notify, save),
|
|
84
|
-
onNavigation: makeOnNavigation(model),
|
|
85
|
-
onNavigationEnd: makeOnNavigationEnd(model),
|
|
86
|
-
} as const
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export type Intent = ReturnType<typeof makeIntent>
|
|
90
|
-
|
|
91
|
-
export const makeSave =
|
|
92
|
-
(model: Model) =>
|
|
93
|
-
(event: NavigationEvent): Effect.Effect<Storage, never, void> =>
|
|
94
|
-
Effect.gen(function* ($) {
|
|
95
|
-
const events = yield* $(model.events)
|
|
96
|
-
const index = yield* $(model.index)
|
|
97
|
-
|
|
98
|
-
// Save to storage
|
|
99
|
-
yield* $(saveToStorage(events, index))
|
|
100
|
-
|
|
101
|
-
// Update current entry
|
|
102
|
-
yield* $(model.currentEntry.set(event.destination))
|
|
103
|
-
|
|
104
|
-
// Update canGoBack
|
|
105
|
-
yield* $(model.canGoBack.set(index > 0))
|
|
106
|
-
|
|
107
|
-
// Update canGoForward
|
|
108
|
-
yield* $(model.canGoForward.set(index < events.length - 1))
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
export const makeReload = (model: Model, notify: Notify, save: Save<Storage>) =>
|
|
112
|
-
Effect.gen(function* ($) {
|
|
113
|
-
const i = yield* $(model.index.get)
|
|
114
|
-
const e = yield* $(model.events)
|
|
115
|
-
const event = e[i]
|
|
116
|
-
const reloadEvent = { ...event, navigationType: NavigationType.Reload }
|
|
117
|
-
|
|
118
|
-
yield* $(notify(reloadEvent))
|
|
119
|
-
yield* $(save(reloadEvent))
|
|
120
|
-
|
|
121
|
-
const location = yield* $(Location)
|
|
122
|
-
|
|
123
|
-
location.reload()
|
|
124
|
-
|
|
125
|
-
return event.destination
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
export const makeReplace =
|
|
129
|
-
(
|
|
130
|
-
model: Model,
|
|
131
|
-
notify: Notify,
|
|
132
|
-
notifyEnd: NotifyEnd,
|
|
133
|
-
save: Save<Storage>,
|
|
134
|
-
history: History,
|
|
135
|
-
base: string,
|
|
136
|
-
) =>
|
|
137
|
-
(url: string, options: NavigateOptions = {}, skipHistory = false) =>
|
|
138
|
-
Effect.gen(function* ($) {
|
|
139
|
-
const location = yield* $(Location)
|
|
140
|
-
const entry = yield* $(model.currentEntry.get)
|
|
141
|
-
const destination: Destination = {
|
|
142
|
-
key: entry.key,
|
|
143
|
-
url: getUrl(url, base, location.origin),
|
|
144
|
-
state: options.state,
|
|
145
|
-
}
|
|
146
|
-
const event: NavigationEvent = {
|
|
147
|
-
destination,
|
|
148
|
-
hashChange: entry.url.hash !== destination.url.hash,
|
|
149
|
-
navigationType: NavigationType.Replace,
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
yield* $(notify(event))
|
|
153
|
-
|
|
154
|
-
if (!skipHistory) {
|
|
155
|
-
history.replaceState({ key: destination.key, state: options.state }, '', url)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const currentIndex = yield* $(model.index)
|
|
159
|
-
|
|
160
|
-
yield* $(
|
|
161
|
-
model.events.update((entries) => {
|
|
162
|
-
const updated = entries.slice(0)
|
|
163
|
-
updated[currentIndex] = event
|
|
164
|
-
return updated
|
|
165
|
-
}),
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
yield* $(save(event))
|
|
169
|
-
yield* $(notifyEnd(event))
|
|
170
|
-
|
|
171
|
-
return destination
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
export const makePush =
|
|
175
|
-
(
|
|
176
|
-
model: Model,
|
|
177
|
-
notify: Notify,
|
|
178
|
-
notifyEnd: NotifyEnd,
|
|
179
|
-
save: Save<Storage>,
|
|
180
|
-
base: string,
|
|
181
|
-
history: History,
|
|
182
|
-
maxEntries: number,
|
|
183
|
-
) =>
|
|
184
|
-
(url: string, options: NavigateOptions = {}, skipHistory = false) =>
|
|
185
|
-
Effect.gen(function* ($) {
|
|
186
|
-
const location = yield* $(Location)
|
|
187
|
-
const entry = yield* $(model.currentEntry.get)
|
|
188
|
-
const destinationUrl = getUrl(url, base, location.origin)
|
|
189
|
-
|
|
190
|
-
if (entry.url.href === destinationUrl.href) {
|
|
191
|
-
return entry
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const destination: Destination = {
|
|
195
|
-
key: yield* $(createKey),
|
|
196
|
-
url: destinationUrl,
|
|
197
|
-
state: options.state,
|
|
198
|
-
}
|
|
199
|
-
const event: NavigationEvent = {
|
|
200
|
-
destination,
|
|
201
|
-
hashChange: entry.url.hash !== destination.url.hash,
|
|
202
|
-
navigationType: NavigationType.Push,
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Notify event handlers
|
|
206
|
-
yield* $(notify(event))
|
|
207
|
-
|
|
208
|
-
if (!skipHistory) {
|
|
209
|
-
history.pushState({ key: destination.key, state: options.state }, '', url)
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const currentIndex = yield* $(model.index)
|
|
213
|
-
|
|
214
|
-
// Remove all entries after the current index
|
|
215
|
-
// and add the new destination to the end
|
|
216
|
-
yield* $(
|
|
217
|
-
model.events.update((entries) => {
|
|
218
|
-
const updated = entries.slice(0, currentIndex + 1)
|
|
219
|
-
updated.push(event)
|
|
220
|
-
return updated.slice(-maxEntries)
|
|
221
|
-
}),
|
|
222
|
-
)
|
|
223
|
-
|
|
224
|
-
// Update the index to the new destination
|
|
225
|
-
yield* $(model.index.update((i) => Math.min(i + 1, maxEntries)))
|
|
226
|
-
|
|
227
|
-
yield* $(save(event))
|
|
228
|
-
yield* $(notifyEnd(event))
|
|
229
|
-
|
|
230
|
-
return destination
|
|
231
|
-
})
|
|
232
|
-
|
|
233
|
-
export const makeGo =
|
|
234
|
-
(model: Model, notify: Notify, notifyEnd: NotifyEnd, save: Save<Storage>, history: History) =>
|
|
235
|
-
(delta: number, skipHistory = false) =>
|
|
236
|
-
Effect.gen(function* ($) {
|
|
237
|
-
const currentEntries = yield* $(model.events)
|
|
238
|
-
const totalEntries = currentEntries.length
|
|
239
|
-
const currentIndex = yield* $(model.index)
|
|
240
|
-
|
|
241
|
-
// Nothing to do here
|
|
242
|
-
if (delta === 0) return currentEntries[currentIndex].destination
|
|
243
|
-
|
|
244
|
-
const nextIndex =
|
|
245
|
-
delta > 0
|
|
246
|
-
? Math.min(currentIndex + delta, totalEntries - 1)
|
|
247
|
-
: Math.min(Math.max(currentIndex + delta, 0))
|
|
248
|
-
const nextEntry = currentEntries[nextIndex]
|
|
249
|
-
|
|
250
|
-
yield* $(
|
|
251
|
-
notify({
|
|
252
|
-
...nextEntry,
|
|
253
|
-
navigationType: nextIndex > currentIndex ? NavigationType.Forward : NavigationType.Back,
|
|
254
|
-
}),
|
|
255
|
-
)
|
|
256
|
-
|
|
257
|
-
if (!skipHistory) {
|
|
258
|
-
history.go(delta)
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
yield* $(model.index.set(nextIndex))
|
|
262
|
-
|
|
263
|
-
yield* $(save(nextEntry))
|
|
264
|
-
|
|
265
|
-
yield* $(notifyEnd(nextEntry))
|
|
266
|
-
|
|
267
|
-
return nextEntry.destination
|
|
268
|
-
})
|
package/src/history.ts
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import * as Cause from '@effect/io/Cause'
|
|
2
|
-
import * as Effect from '@effect/io/Effect'
|
|
3
|
-
import * as Fiber from '@effect/io/Fiber'
|
|
4
|
-
import * as Runtime from '@effect/io/Runtime'
|
|
5
|
-
import * as Scope from '@effect/io/Scope'
|
|
6
|
-
import { History } from '@typed/dom'
|
|
7
|
-
import * as Fx from '@typed/fx'
|
|
8
|
-
|
|
9
|
-
import { Destination, NavigationError } from './Navigation.js'
|
|
10
|
-
import { DomIntent } from './dom-intent.js'
|
|
11
|
-
|
|
12
|
-
export type HistoryEvent = PushStateEvent | ReplaceStateEvent | GoEvent | BackEvent | ForwardEvent
|
|
13
|
-
|
|
14
|
-
export interface PushStateEvent {
|
|
15
|
-
readonly _tag: 'PushState'
|
|
16
|
-
readonly state: unknown
|
|
17
|
-
readonly url: string
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface ReplaceStateEvent {
|
|
21
|
-
readonly _tag: 'ReplaceState'
|
|
22
|
-
readonly state: unknown
|
|
23
|
-
readonly url: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface GoEvent {
|
|
27
|
-
readonly _tag: 'Go'
|
|
28
|
-
readonly delta: number
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface BackEvent {
|
|
32
|
-
readonly _tag: 'Back'
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface ForwardEvent {
|
|
36
|
-
readonly _tag: 'Forward'
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export const patchHistory: Effect.Effect<
|
|
40
|
-
History | Scope.Scope,
|
|
41
|
-
never,
|
|
42
|
-
readonly [History, Fx.Subject<never, HistoryEvent>]
|
|
43
|
-
> = Effect.gen(function* ($) {
|
|
44
|
-
const history = yield* $(History)
|
|
45
|
-
|
|
46
|
-
const stateDescriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(history), 'state')
|
|
47
|
-
|
|
48
|
-
// Create a clone that always operates on the original history
|
|
49
|
-
const clone: History = {
|
|
50
|
-
get length() {
|
|
51
|
-
return history.length
|
|
52
|
-
},
|
|
53
|
-
get scrollRestoration() {
|
|
54
|
-
return history.scrollRestoration
|
|
55
|
-
},
|
|
56
|
-
set scrollRestoration(value) {
|
|
57
|
-
history.scrollRestoration = value
|
|
58
|
-
},
|
|
59
|
-
get state() {
|
|
60
|
-
const state = stateDescriptor?.get?.call(history)
|
|
61
|
-
|
|
62
|
-
if (state && state.key && state.state) return state.state
|
|
63
|
-
|
|
64
|
-
return state
|
|
65
|
-
},
|
|
66
|
-
back: history.back.bind(history),
|
|
67
|
-
forward: history.forward.bind(history),
|
|
68
|
-
go: history.go.bind(history),
|
|
69
|
-
pushState: history.pushState.bind(history),
|
|
70
|
-
replaceState: history.replaceState.bind(history),
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const scope = yield* $(Effect.scope)
|
|
74
|
-
const historyEvents = Fx.makeSubject<never, HistoryEvent>()
|
|
75
|
-
const runtime = yield* $(Effect.runtime<never>())
|
|
76
|
-
const runFork = Runtime.runFork(runtime)
|
|
77
|
-
const cleanup = patchHistory_(history, (event: HistoryEvent) =>
|
|
78
|
-
runFork(Effect.flatMap(Effect.forkIn(historyEvents.event(event), scope), Fiber.join)),
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
// unpatch history upon finalization
|
|
82
|
-
yield* $(Effect.addFinalizer(() => Effect.sync(cleanup)))
|
|
83
|
-
|
|
84
|
-
return [clone, historyEvents]
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
function patchHistory_(history: History, sendEvent: (event: HistoryEvent) => void) {
|
|
88
|
-
const pushState = history.pushState
|
|
89
|
-
const replaceState = history.replaceState
|
|
90
|
-
const go = history.go
|
|
91
|
-
const back = history.back
|
|
92
|
-
const forward = history.forward
|
|
93
|
-
|
|
94
|
-
history.pushState = function (state, _, url) {
|
|
95
|
-
if (url) sendEvent({ _tag: 'PushState', state, url: url.toString() })
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
history.replaceState = function (state, _, url) {
|
|
99
|
-
if (url) sendEvent({ _tag: 'ReplaceState', state, url: url.toString() })
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
history.go = function (delta) {
|
|
103
|
-
if (!delta) return
|
|
104
|
-
|
|
105
|
-
sendEvent({ _tag: 'Go', delta })
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
history.back = function () {
|
|
109
|
-
sendEvent({ _tag: 'Back' })
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
history.forward = function () {
|
|
113
|
-
sendEvent({ _tag: 'Forward' })
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const stateDescriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(history), 'state')
|
|
117
|
-
|
|
118
|
-
// Override state to always return the original state
|
|
119
|
-
Object.defineProperty(history, 'state', {
|
|
120
|
-
get() {
|
|
121
|
-
const state = stateDescriptor?.get?.call(history)
|
|
122
|
-
|
|
123
|
-
if (state && state.key && state.state) return state.state
|
|
124
|
-
|
|
125
|
-
return state
|
|
126
|
-
},
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
// Reset history to original state
|
|
130
|
-
return () => {
|
|
131
|
-
history.pushState = pushState
|
|
132
|
-
history.replaceState = replaceState
|
|
133
|
-
history.go = go
|
|
134
|
-
history.back = back
|
|
135
|
-
history.forward = forward
|
|
136
|
-
|
|
137
|
-
if (stateDescriptor) {
|
|
138
|
-
Object.defineProperty(history, 'state', stateDescriptor)
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export function onHistoryEvent(
|
|
144
|
-
event: HistoryEvent,
|
|
145
|
-
intent: DomIntent,
|
|
146
|
-
): Effect.Effect<
|
|
147
|
-
History | Location | Storage,
|
|
148
|
-
Cause.NoSuchElementException | NavigationError,
|
|
149
|
-
Destination
|
|
150
|
-
> {
|
|
151
|
-
return Effect.gen(function* ($) {
|
|
152
|
-
switch (event._tag) {
|
|
153
|
-
case 'PushState':
|
|
154
|
-
return yield* $(intent.push(event.url, { state: event.state }, true))
|
|
155
|
-
case 'ReplaceState':
|
|
156
|
-
return yield* $(intent.replace(event.url, { state: event.state }, true))
|
|
157
|
-
case 'Back':
|
|
158
|
-
return yield* $(intent.back(true))
|
|
159
|
-
case 'Forward':
|
|
160
|
-
return yield* $(intent.forward(true))
|
|
161
|
-
case 'Go':
|
|
162
|
-
return yield* $(intent.go(event.delta, true))
|
|
163
|
-
}
|
|
164
|
-
})
|
|
165
|
-
}
|
package/src/json.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { Destination, NavigationEvent } from './Navigation.js'
|
|
2
|
-
|
|
3
|
-
export type NavigationEventJson = {
|
|
4
|
-
readonly [K in keyof NavigationEvent]: NavigationEvent[K] extends Destination
|
|
5
|
-
? DestinationJson
|
|
6
|
-
: NavigationEvent[K]
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
type DestinationJson = {
|
|
10
|
-
readonly [K in keyof Destination]: Destination[K] extends URL ? string : Destination[K]
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const decodeDestination = (d: DestinationJson): Destination => ({
|
|
14
|
-
...d,
|
|
15
|
-
url: new URL(d.url),
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
export const decodeNavigationEvent = (event: NavigationEventJson): NavigationEvent => ({
|
|
19
|
-
...event,
|
|
20
|
-
destination: decodeDestination(event.destination),
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
export const encodeEvent = (event: NavigationEvent): NavigationEventJson => ({
|
|
24
|
-
...event,
|
|
25
|
-
destination: encodeDestination(event.destination),
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
export const encodeDestination = (destination: Destination): DestinationJson => ({
|
|
29
|
-
...destination,
|
|
30
|
-
url: destination.url.href,
|
|
31
|
-
})
|