@furystack/shades 13.2.2 → 15.0.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/CHANGELOG.md +157 -0
- package/esm/component-factory.spec.d.ts.map +1 -1
- package/esm/component-factory.spec.js +2 -2
- package/esm/component-factory.spec.js.map +1 -1
- package/esm/components/define-nested-routes.d.ts +27 -0
- package/esm/components/define-nested-routes.d.ts.map +1 -0
- package/esm/components/define-nested-routes.js +26 -0
- package/esm/components/define-nested-routes.js.map +1 -0
- package/esm/components/define-nested-routes.spec.d.ts +2 -0
- package/esm/components/define-nested-routes.spec.d.ts.map +1 -0
- package/esm/components/define-nested-routes.spec.js +81 -0
- package/esm/components/define-nested-routes.spec.js.map +1 -0
- package/esm/components/index.d.ts +3 -3
- package/esm/components/index.d.ts.map +1 -1
- package/esm/components/index.js +3 -3
- package/esm/components/index.js.map +1 -1
- package/esm/components/nested-hooks.d.ts +62 -0
- package/esm/components/nested-hooks.d.ts.map +1 -0
- package/esm/components/nested-hooks.js +73 -0
- package/esm/components/nested-hooks.js.map +1 -0
- package/esm/components/nested-hooks.spec.d.ts +2 -0
- package/esm/components/nested-hooks.spec.d.ts.map +1 -0
- package/esm/components/nested-hooks.spec.js +211 -0
- package/esm/components/nested-hooks.spec.js.map +1 -0
- package/esm/components/nested-navigate.d.ts +52 -21
- package/esm/components/nested-navigate.d.ts.map +1 -1
- package/esm/components/nested-navigate.js +32 -17
- package/esm/components/nested-navigate.js.map +1 -1
- package/esm/components/nested-navigate.spec.js +109 -19
- package/esm/components/nested-navigate.spec.js.map +1 -1
- package/esm/components/nested-replace.d.ts +36 -0
- package/esm/components/nested-replace.d.ts.map +1 -0
- package/esm/components/nested-replace.js +38 -0
- package/esm/components/nested-replace.js.map +1 -0
- package/esm/components/nested-replace.spec.d.ts +2 -0
- package/esm/components/nested-replace.spec.d.ts.map +1 -0
- package/esm/components/nested-replace.spec.js +59 -0
- package/esm/components/nested-replace.spec.js.map +1 -0
- package/esm/components/nested-route-link.d.ts +36 -26
- package/esm/components/nested-route-link.d.ts.map +1 -1
- package/esm/components/nested-route-link.js +18 -20
- package/esm/components/nested-route-link.js.map +1 -1
- package/esm/components/nested-route-link.spec.js +67 -17
- package/esm/components/nested-route-link.spec.js.map +1 -1
- package/esm/components/nested-route-types.d.ts +79 -2
- package/esm/components/nested-route-types.d.ts.map +1 -1
- package/esm/components/nested-router.d.ts +77 -7
- package/esm/components/nested-router.d.ts.map +1 -1
- package/esm/components/nested-router.js +139 -13
- package/esm/components/nested-router.js.map +1 -1
- package/esm/components/nested-router.spec.js +283 -52
- package/esm/components/nested-router.spec.js.map +1 -1
- package/esm/services/location-service.d.ts +39 -42
- package/esm/services/location-service.d.ts.map +1 -1
- package/esm/services/location-service.js +115 -134
- package/esm/services/location-service.js.map +1 -1
- package/esm/services/location-service.spec.js +59 -24
- package/esm/services/location-service.spec.js.map +1 -1
- package/esm/services/route-match-service.d.ts +3 -2
- package/esm/services/route-match-service.d.ts.map +1 -1
- package/esm/services/route-match-service.js +13 -22
- package/esm/services/route-match-service.js.map +1 -1
- package/esm/services/route-match-service.spec.js +42 -49
- package/esm/services/route-match-service.spec.js.map +1 -1
- package/esm/services/route-meta-utils.d.ts +28 -6
- package/esm/services/route-meta-utils.d.ts.map +1 -1
- package/esm/services/route-meta-utils.js +18 -4
- package/esm/services/route-meta-utils.js.map +1 -1
- package/esm/services/route-meta-utils.spec.js +60 -5
- package/esm/services/route-meta-utils.spec.js.map +1 -1
- package/esm/services/screen-service.d.ts +13 -72
- package/esm/services/screen-service.d.ts.map +1 -1
- package/esm/services/screen-service.js +45 -137
- package/esm/services/screen-service.js.map +1 -1
- package/esm/services/screen-service.spec.js +19 -18
- package/esm/services/screen-service.spec.js.map +1 -1
- package/esm/services/spatial-navigation-service.d.ts +17 -39
- package/esm/services/spatial-navigation-service.d.ts.map +1 -1
- package/esm/services/spatial-navigation-service.js +280 -313
- package/esm/services/spatial-navigation-service.js.map +1 -1
- package/esm/services/spatial-navigation-service.spec.js +163 -158
- package/esm/services/spatial-navigation-service.spec.js.map +1 -1
- package/esm/shade.d.ts +1 -1
- package/esm/shade.d.ts.map +1 -1
- package/esm/shade.js +10 -7
- package/esm/shade.js.map +1 -1
- package/esm/vnode.d.ts +1 -1
- package/esm/vnode.js +1 -1
- package/package.json +5 -4
- package/src/component-factory.spec.tsx +3 -2
- package/src/components/define-nested-routes.spec.ts +91 -0
- package/src/components/define-nested-routes.ts +27 -0
- package/src/components/index.ts +3 -3
- package/src/components/nested-hooks.spec.ts +254 -0
- package/src/components/nested-hooks.ts +109 -0
- package/src/components/nested-navigate.spec.ts +136 -32
- package/src/components/nested-navigate.ts +75 -26
- package/src/components/nested-replace.spec.ts +89 -0
- package/src/components/nested-replace.ts +46 -0
- package/src/components/nested-route-link.spec.tsx +90 -22
- package/src/components/nested-route-link.tsx +54 -29
- package/src/components/nested-route-types.ts +82 -2
- package/src/components/nested-router.spec.tsx +334 -51
- package/src/components/nested-router.tsx +184 -24
- package/src/services/location-service.spec.ts +68 -25
- package/src/services/location-service.tsx +156 -122
- package/src/services/route-match-service.spec.ts +41 -28
- package/src/services/route-match-service.ts +17 -8
- package/src/services/route-meta-utils.spec.ts +78 -10
- package/src/services/route-meta-utils.ts +44 -15
- package/src/services/screen-service.spec.ts +19 -18
- package/src/services/screen-service.ts +65 -110
- package/src/services/spatial-navigation-service.spec.ts +165 -160
- package/src/services/spatial-navigation-service.ts +299 -287
- package/src/shade.ts +11 -8
- package/src/vnode.ts +1 -1
- package/esm/components/link-to-route.d.ts +0 -10
- package/esm/components/link-to-route.d.ts.map +0 -1
- package/esm/components/link-to-route.js +0 -16
- package/esm/components/link-to-route.js.map +0 -1
- package/esm/components/link-to-route.spec.d.ts +0 -2
- package/esm/components/link-to-route.spec.d.ts.map +0 -1
- package/esm/components/link-to-route.spec.js +0 -30
- package/esm/components/link-to-route.spec.js.map +0 -1
- package/esm/components/route-link.d.ts +0 -10
- package/esm/components/route-link.d.ts.map +0 -1
- package/esm/components/route-link.js +0 -25
- package/esm/components/route-link.js.map +0 -1
- package/esm/components/route-link.spec.d.ts +0 -2
- package/esm/components/route-link.spec.d.ts.map +0 -1
- package/esm/components/route-link.spec.js +0 -34
- package/esm/components/route-link.spec.js.map +0 -1
- package/esm/components/router.d.ts +0 -44
- package/esm/components/router.d.ts.map +0 -1
- package/esm/components/router.js +0 -66
- package/esm/components/router.js.map +0 -1
- package/esm/components/router.spec.d.ts +0 -2
- package/esm/components/router.spec.d.ts.map +0 -1
- package/esm/components/router.spec.js +0 -161
- package/esm/components/router.spec.js.map +0 -1
- package/src/components/link-to-route.spec.tsx +0 -45
- package/src/components/link-to-route.tsx +0 -26
- package/src/components/route-link.spec.tsx +0 -44
- package/src/components/route-link.tsx +0 -29
- package/src/components/router.spec.tsx +0 -217
- package/src/components/router.tsx +0 -103
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { Injector } from '@furystack/inject'
|
|
2
|
-
import { usingAsync } from '@furystack/utils'
|
|
3
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
4
|
-
import { initializeShadeRoot } from '../initialize.js'
|
|
5
|
-
import { LocationService } from '../services/location-service.js'
|
|
6
|
-
import { createComponent } from '../shade-component.js'
|
|
7
|
-
import { flushUpdates } from '../shade.js'
|
|
8
|
-
import { RouteLink } from './route-link.js'
|
|
9
|
-
|
|
10
|
-
describe('RouteLink', () => {
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
document.body.innerHTML = '<div id="root"></div>'
|
|
13
|
-
})
|
|
14
|
-
afterEach(() => {
|
|
15
|
-
document.body.innerHTML = ''
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it('Shuld display the loader and completed state', async () => {
|
|
19
|
-
await usingAsync(new Injector(), async (injector) => {
|
|
20
|
-
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
21
|
-
|
|
22
|
-
const onRouteChange = vi.fn()
|
|
23
|
-
|
|
24
|
-
injector.getInstance(LocationService).onLocationPathChanged.subscribe(onRouteChange)
|
|
25
|
-
|
|
26
|
-
initializeShadeRoot({
|
|
27
|
-
injector,
|
|
28
|
-
rootElement,
|
|
29
|
-
jsxElement: (
|
|
30
|
-
<RouteLink id="route" href="/subroute">
|
|
31
|
-
Link
|
|
32
|
-
</RouteLink>
|
|
33
|
-
),
|
|
34
|
-
})
|
|
35
|
-
await flushUpdates()
|
|
36
|
-
expect(document.body.innerHTML).toMatchInlineSnapshot(
|
|
37
|
-
`"<div id="root"><a is="route-link" id="route" href="/subroute">Link</a></div>"`,
|
|
38
|
-
)
|
|
39
|
-
expect(onRouteChange).not.toBeCalled()
|
|
40
|
-
document.getElementById('route')?.click()
|
|
41
|
-
expect(onRouteChange).toBeCalledTimes(1)
|
|
42
|
-
})
|
|
43
|
-
})
|
|
44
|
-
})
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { PartialElement } from '../models/partial-element.js'
|
|
2
|
-
import { LocationService } from '../services/location-service.js'
|
|
3
|
-
import { createComponent } from '../shade-component.js'
|
|
4
|
-
import { Shade } from '../shade.js'
|
|
5
|
-
|
|
6
|
-
/** @deprecated Use `NestedRouteLinkProps` from `nested-route-link` instead */
|
|
7
|
-
export type RouteLinkProps = PartialElement<Omit<HTMLAnchorElement, 'onclick'>>
|
|
8
|
-
|
|
9
|
-
/** @deprecated Use `NestedRouteLink` from `nested-route-link` instead */
|
|
10
|
-
export const RouteLink = Shade<RouteLinkProps>({
|
|
11
|
-
customElementName: 'route-link',
|
|
12
|
-
elementBase: HTMLAnchorElement,
|
|
13
|
-
elementBaseName: 'a',
|
|
14
|
-
css: {
|
|
15
|
-
color: 'inherit',
|
|
16
|
-
textDecoration: 'inherit',
|
|
17
|
-
},
|
|
18
|
-
render: ({ children, props, injector, useHostProps }) => {
|
|
19
|
-
useHostProps({
|
|
20
|
-
onclick: (ev: MouseEvent) => {
|
|
21
|
-
ev.preventDefault()
|
|
22
|
-
// eslint-disable-next-line furystack/prefer-location-service -- Deprecated framework component; must call pushState directly.
|
|
23
|
-
history.pushState('', props.title || '', props.href)
|
|
24
|
-
injector.getInstance(LocationService).updateState()
|
|
25
|
-
},
|
|
26
|
-
})
|
|
27
|
-
return <>{children}</>
|
|
28
|
-
},
|
|
29
|
-
})
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
import { Injector } from '@furystack/inject'
|
|
2
|
-
import { sleepAsync, usingAsync } from '@furystack/utils'
|
|
3
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
4
|
-
import { initializeShadeRoot } from '../initialize.js'
|
|
5
|
-
import { LocationService } from '../services/location-service.js'
|
|
6
|
-
import { createComponent } from '../shade-component.js'
|
|
7
|
-
import { flushUpdates } from '../shade.js'
|
|
8
|
-
import { RouteLink } from './route-link.js'
|
|
9
|
-
import { Router, type Route } from './router.js'
|
|
10
|
-
|
|
11
|
-
describe('Router', () => {
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
document.body.innerHTML = '<div id="root"></div>'
|
|
14
|
-
})
|
|
15
|
-
afterEach(() => {
|
|
16
|
-
document.body.innerHTML = ''
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
it('should skip intermediate route when navigating rapidly (latest wins)', async () => {
|
|
20
|
-
await usingAsync(new Injector(), async (injector) => {
|
|
21
|
-
history.pushState(null, '', '/route-a')
|
|
22
|
-
|
|
23
|
-
const callOrder: string[] = []
|
|
24
|
-
|
|
25
|
-
const onVisitA = vi.fn(async () => {
|
|
26
|
-
callOrder.push('visit-a')
|
|
27
|
-
})
|
|
28
|
-
const onLeaveA = vi.fn(async () => {
|
|
29
|
-
callOrder.push('leave-a')
|
|
30
|
-
})
|
|
31
|
-
const onVisitB = vi.fn(async () => {
|
|
32
|
-
await sleepAsync(200)
|
|
33
|
-
callOrder.push('visit-b')
|
|
34
|
-
})
|
|
35
|
-
const onLeaveB = vi.fn(async () => {
|
|
36
|
-
callOrder.push('leave-b')
|
|
37
|
-
})
|
|
38
|
-
const onVisitC = vi.fn(async () => {
|
|
39
|
-
callOrder.push('visit-c')
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
const routeA: Route = {
|
|
43
|
-
url: '/route-a',
|
|
44
|
-
component: () => <div id="content">route-a</div>,
|
|
45
|
-
onVisit: onVisitA,
|
|
46
|
-
onLeave: onLeaveA,
|
|
47
|
-
}
|
|
48
|
-
const routeB: Route = {
|
|
49
|
-
url: '/route-b',
|
|
50
|
-
component: () => <div id="content">route-b</div>,
|
|
51
|
-
onVisit: onVisitB,
|
|
52
|
-
onLeave: onLeaveB,
|
|
53
|
-
}
|
|
54
|
-
const routeC: Route = {
|
|
55
|
-
url: '/route-c',
|
|
56
|
-
component: () => <div id="content">route-c</div>,
|
|
57
|
-
onVisit: onVisitC,
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
61
|
-
|
|
62
|
-
initializeShadeRoot({
|
|
63
|
-
injector,
|
|
64
|
-
rootElement,
|
|
65
|
-
jsxElement: (
|
|
66
|
-
<div>
|
|
67
|
-
<RouteLink id="go-a" href="/route-a">
|
|
68
|
-
a
|
|
69
|
-
</RouteLink>
|
|
70
|
-
<RouteLink id="go-b" href="/route-b">
|
|
71
|
-
b
|
|
72
|
-
</RouteLink>
|
|
73
|
-
<RouteLink id="go-c" href="/route-c">
|
|
74
|
-
c
|
|
75
|
-
</RouteLink>
|
|
76
|
-
<Router routes={[routeA, routeB, routeC]} />
|
|
77
|
-
</div>
|
|
78
|
-
),
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
const getContent = () => document.getElementById('content')?.innerHTML
|
|
82
|
-
const clickOn = (name: string) => document.getElementById(name)?.click()
|
|
83
|
-
|
|
84
|
-
// --- Initial load at /route-a ---
|
|
85
|
-
await flushUpdates()
|
|
86
|
-
expect(getContent()).toBe('route-a')
|
|
87
|
-
expect(onVisitA).toHaveBeenCalledTimes(1)
|
|
88
|
-
|
|
89
|
-
// --- Rapid navigation: click B then immediately C ---
|
|
90
|
-
callOrder.length = 0
|
|
91
|
-
clickOn('go-b')
|
|
92
|
-
clickOn('go-c')
|
|
93
|
-
|
|
94
|
-
// Wait long enough for both transitions to settle (onVisitB has 200ms delay)
|
|
95
|
-
await sleepAsync(500)
|
|
96
|
-
|
|
97
|
-
// The final destination should be route-c
|
|
98
|
-
expect(getContent()).toBe('route-c')
|
|
99
|
-
expect(onVisitC).toHaveBeenCalledTimes(1)
|
|
100
|
-
|
|
101
|
-
// route-b's onVisit should have been abandoned
|
|
102
|
-
expect(callOrder).not.toContain('visit-b')
|
|
103
|
-
})
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
it('Shuld display the loader and completed state', async () => {
|
|
107
|
-
await usingAsync(new Injector(), async (injector) => {
|
|
108
|
-
history.pushState(null, '', '/')
|
|
109
|
-
|
|
110
|
-
const onVisit = vi.fn()
|
|
111
|
-
const onLeave = vi.fn()
|
|
112
|
-
const onLastLeave = vi.fn()
|
|
113
|
-
|
|
114
|
-
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
115
|
-
|
|
116
|
-
const onRouteChange = vi.fn()
|
|
117
|
-
|
|
118
|
-
injector.getInstance(LocationService).onLocationPathChanged.subscribe(onRouteChange)
|
|
119
|
-
|
|
120
|
-
const route1: Route = {
|
|
121
|
-
url: '/route-a',
|
|
122
|
-
component: () => <div id="content">route-a</div>,
|
|
123
|
-
onVisit,
|
|
124
|
-
onLeave,
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const route2: Route<{ id: string }> = {
|
|
128
|
-
url: '/route-b{/:id}',
|
|
129
|
-
component: ({ match }) => <div id="content">route-b{match.params.id}</div>,
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const route3: Route = {
|
|
133
|
-
url: '/route-c',
|
|
134
|
-
component: () => <div id="content">route-c</div>,
|
|
135
|
-
onLeave: onLastLeave,
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const route4 = { url: '/', component: () => <div id="content">home</div> }
|
|
139
|
-
|
|
140
|
-
initializeShadeRoot({
|
|
141
|
-
injector,
|
|
142
|
-
rootElement,
|
|
143
|
-
jsxElement: (
|
|
144
|
-
<div>
|
|
145
|
-
<RouteLink id="home" href="/">
|
|
146
|
-
home
|
|
147
|
-
</RouteLink>
|
|
148
|
-
<RouteLink id="a" href="/route-a">
|
|
149
|
-
a
|
|
150
|
-
</RouteLink>
|
|
151
|
-
<RouteLink id="b" href="/route-b">
|
|
152
|
-
b
|
|
153
|
-
</RouteLink>
|
|
154
|
-
<RouteLink id="b-with-id" href="/route-b/123">
|
|
155
|
-
b-with-id
|
|
156
|
-
</RouteLink>
|
|
157
|
-
<RouteLink id="c" href="/route-c">
|
|
158
|
-
c
|
|
159
|
-
</RouteLink>
|
|
160
|
-
<RouteLink id="x" href="/route-x">
|
|
161
|
-
x
|
|
162
|
-
</RouteLink>
|
|
163
|
-
<Router routes={[route1, route2, route3, route4]} notFound={<div id="content">not found</div>} />
|
|
164
|
-
</div>
|
|
165
|
-
),
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
const getContent = () => document.getElementById('content')?.innerHTML
|
|
169
|
-
const getLocation = () => location.pathname
|
|
170
|
-
|
|
171
|
-
const clickOn = (name: string) => document.getElementById(name)?.click()
|
|
172
|
-
|
|
173
|
-
await flushUpdates()
|
|
174
|
-
|
|
175
|
-
expect(getLocation()).toBe('/')
|
|
176
|
-
expect(getContent()).toBe('home')
|
|
177
|
-
|
|
178
|
-
expect(onVisit).not.toBeCalled()
|
|
179
|
-
|
|
180
|
-
clickOn('a')
|
|
181
|
-
await flushUpdates()
|
|
182
|
-
expect(getContent()).toBe('route-a')
|
|
183
|
-
expect(getLocation()).toBe('/route-a')
|
|
184
|
-
expect(onRouteChange).toBeCalledTimes(1)
|
|
185
|
-
expect(onVisit).toBeCalledTimes(1)
|
|
186
|
-
|
|
187
|
-
clickOn('a')
|
|
188
|
-
await flushUpdates()
|
|
189
|
-
expect(onVisit).toBeCalledTimes(1)
|
|
190
|
-
expect(onLeave).not.toBeCalled()
|
|
191
|
-
|
|
192
|
-
clickOn('b')
|
|
193
|
-
await flushUpdates()
|
|
194
|
-
expect(onLeave).toBeCalledTimes(1)
|
|
195
|
-
|
|
196
|
-
expect(getContent()).toBe('route-b')
|
|
197
|
-
expect(getLocation()).toBe('/route-b')
|
|
198
|
-
|
|
199
|
-
clickOn('b-with-id')
|
|
200
|
-
await flushUpdates()
|
|
201
|
-
expect(getContent()).toBe('route-b123')
|
|
202
|
-
expect(getLocation()).toBe('/route-b/123')
|
|
203
|
-
|
|
204
|
-
clickOn('c')
|
|
205
|
-
await flushUpdates()
|
|
206
|
-
expect(getContent()).toBe('route-c')
|
|
207
|
-
expect(getLocation()).toBe('/route-c')
|
|
208
|
-
|
|
209
|
-
expect(onLastLeave).not.toBeCalled()
|
|
210
|
-
clickOn('x')
|
|
211
|
-
await flushUpdates()
|
|
212
|
-
expect(getContent()).toBe('not found')
|
|
213
|
-
expect(getLocation()).toBe('/route-x')
|
|
214
|
-
expect(onLastLeave).toBeCalledTimes(1)
|
|
215
|
-
})
|
|
216
|
-
})
|
|
217
|
-
})
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { ObservableAlreadyDisposedError } from '@furystack/utils'
|
|
2
|
-
import type { MatchOptions, MatchResult } from 'path-to-regexp'
|
|
3
|
-
import { match } from 'path-to-regexp'
|
|
4
|
-
import type { RenderOptions } from '../models/render-options.js'
|
|
5
|
-
import { LocationService } from '../services/location-service.js'
|
|
6
|
-
import { createComponent } from '../shade-component.js'
|
|
7
|
-
import { Shade } from '../shade.js'
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @deprecated Use NestedRouter instead
|
|
11
|
-
*/
|
|
12
|
-
export interface Route<TMatchResult = unknown> {
|
|
13
|
-
url: string
|
|
14
|
-
component: (options: {
|
|
15
|
-
currentUrl: string
|
|
16
|
-
match: MatchResult<TMatchResult extends object ? TMatchResult : object>
|
|
17
|
-
}) => JSX.Element
|
|
18
|
-
routingOptions?: MatchOptions
|
|
19
|
-
onVisit?: (options: RenderOptions<unknown> & { element: JSX.Element }) => Promise<void>
|
|
20
|
-
onLeave?: (options: RenderOptions<unknown> & { element: JSX.Element }) => Promise<void>
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* @deprecated Use NestedRouterProps instead
|
|
25
|
-
*/
|
|
26
|
-
export interface RouterProps {
|
|
27
|
-
style?: CSSStyleDeclaration
|
|
28
|
-
routes: Array<Route<any>>
|
|
29
|
-
notFound?: JSX.Element
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* @deprecated Use NestedRouterState instead
|
|
34
|
-
*/
|
|
35
|
-
export interface RouterState {
|
|
36
|
-
activeRoute?: Route<unknown> | null
|
|
37
|
-
activeRouteParams?: unknown
|
|
38
|
-
jsx: JSX.Element
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* @deprecated Use NestedRouter instead
|
|
43
|
-
*/
|
|
44
|
-
export const Router = Shade<RouterProps>({
|
|
45
|
-
customElementName: 'shade-router',
|
|
46
|
-
render: (options) => {
|
|
47
|
-
const { useState, useObservable, injector } = options
|
|
48
|
-
const [versionRef] = useState('navVersion', { current: 0 })
|
|
49
|
-
const [state, setState] = useState<RouterState>('routerState', {
|
|
50
|
-
jsx: <div />,
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
const updateUrl = async (currentUrl: string) => {
|
|
54
|
-
const [lastState] = useState<RouterState>('routerState', state)
|
|
55
|
-
const { activeRoute: lastRoute, activeRouteParams: lastRouteParams, jsx: lastJsx } = lastState
|
|
56
|
-
try {
|
|
57
|
-
for (const route of options.props.routes) {
|
|
58
|
-
const matchFn = match(route.url, route.routingOptions)
|
|
59
|
-
const matchResult = matchFn(currentUrl)
|
|
60
|
-
if (matchResult) {
|
|
61
|
-
if (route !== lastRoute || JSON.stringify(lastRouteParams) !== JSON.stringify(matchResult.params)) {
|
|
62
|
-
const version = ++versionRef.current
|
|
63
|
-
await lastRoute?.onLeave?.({ ...options, element: lastState.jsx })
|
|
64
|
-
if (version !== versionRef.current) return
|
|
65
|
-
const newJsx = route.component({ currentUrl, match: matchResult })
|
|
66
|
-
setState({ jsx: newJsx, activeRoute: route, activeRouteParams: matchResult.params })
|
|
67
|
-
await route.onVisit?.({ ...options, element: newJsx })
|
|
68
|
-
if (version !== versionRef.current) return
|
|
69
|
-
}
|
|
70
|
-
return
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (lastRoute !== null) {
|
|
75
|
-
const version = ++versionRef.current
|
|
76
|
-
await lastRoute?.onLeave?.({ ...options, element: lastJsx })
|
|
77
|
-
if (version !== versionRef.current) return
|
|
78
|
-
setState({
|
|
79
|
-
jsx: options.props.notFound || <div />,
|
|
80
|
-
activeRoute: null,
|
|
81
|
-
activeRouteParams: null,
|
|
82
|
-
})
|
|
83
|
-
}
|
|
84
|
-
} catch (e) {
|
|
85
|
-
if (!(e instanceof ObservableAlreadyDisposedError)) {
|
|
86
|
-
throw e
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const [locationPath] = useObservable(
|
|
92
|
-
'locationPathChanged',
|
|
93
|
-
injector.getInstance(LocationService).onLocationPathChanged,
|
|
94
|
-
{
|
|
95
|
-
onChange: (newValue) => {
|
|
96
|
-
void updateUrl(newValue)
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
)
|
|
100
|
-
void updateUrl(locationPath)
|
|
101
|
-
return state.jsx
|
|
102
|
-
},
|
|
103
|
-
})
|