@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,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createInjector } from '@furystack/inject'
|
|
2
2
|
import { usingAsync } from '@furystack/utils'
|
|
3
3
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
4
4
|
import { SpatialNavigationService, configureSpatialNavigation } from './spatial-navigation-service.js'
|
|
@@ -44,29 +44,30 @@ describe('SpatialNavigationService', () => {
|
|
|
44
44
|
})
|
|
45
45
|
|
|
46
46
|
it('Should be constructed via injector', async () => {
|
|
47
|
-
await usingAsync(
|
|
48
|
-
const s = i.
|
|
49
|
-
expect(s).
|
|
47
|
+
await usingAsync(createInjector(), async (i) => {
|
|
48
|
+
const s = i.get(SpatialNavigationService)
|
|
49
|
+
expect(s).toBeDefined()
|
|
50
|
+
expect(typeof s.moveFocus).toBe('function')
|
|
50
51
|
})
|
|
51
52
|
})
|
|
52
53
|
|
|
53
54
|
it('Should be enabled by default', async () => {
|
|
54
|
-
await usingAsync(
|
|
55
|
-
const s = i.
|
|
55
|
+
await usingAsync(createInjector(), async (i) => {
|
|
56
|
+
const s = i.get(SpatialNavigationService)
|
|
56
57
|
expect(s.enabled.getValue()).toBe(true)
|
|
57
58
|
})
|
|
58
59
|
})
|
|
59
60
|
|
|
60
61
|
it('Should have null activeSection initially', async () => {
|
|
61
|
-
await usingAsync(
|
|
62
|
-
const s = i.
|
|
62
|
+
await usingAsync(createInjector(), async (i) => {
|
|
63
|
+
const s = i.get(SpatialNavigationService)
|
|
63
64
|
expect(s.activeSection.getValue()).toBeNull()
|
|
64
65
|
})
|
|
65
66
|
})
|
|
66
67
|
|
|
67
68
|
describe('focus movement', () => {
|
|
68
69
|
it('Should move focus to the right', async () => {
|
|
69
|
-
await usingAsync(
|
|
70
|
+
await usingAsync(createInjector(), async (i) => {
|
|
70
71
|
const left = createButton('left', { left: 0, top: 0, width: 50, height: 50 })
|
|
71
72
|
const right = createButton('right', { left: 100, top: 0, width: 50, height: 50 })
|
|
72
73
|
document.body.append(left, right)
|
|
@@ -74,56 +75,56 @@ describe('SpatialNavigationService', () => {
|
|
|
74
75
|
left.focus()
|
|
75
76
|
expect(document.activeElement).toBe(left)
|
|
76
77
|
|
|
77
|
-
const s = i.
|
|
78
|
+
const s = i.get(SpatialNavigationService)
|
|
78
79
|
s.moveFocus('right')
|
|
79
80
|
expect(document.activeElement).toBe(right)
|
|
80
81
|
})
|
|
81
82
|
})
|
|
82
83
|
|
|
83
84
|
it('Should move focus to the left', async () => {
|
|
84
|
-
await usingAsync(
|
|
85
|
+
await usingAsync(createInjector(), async (i) => {
|
|
85
86
|
const left = createButton('left', { left: 0, top: 0, width: 50, height: 50 })
|
|
86
87
|
const right = createButton('right', { left: 100, top: 0, width: 50, height: 50 })
|
|
87
88
|
document.body.append(left, right)
|
|
88
89
|
|
|
89
90
|
right.focus()
|
|
90
91
|
|
|
91
|
-
const s = i.
|
|
92
|
+
const s = i.get(SpatialNavigationService)
|
|
92
93
|
s.moveFocus('left')
|
|
93
94
|
expect(document.activeElement).toBe(left)
|
|
94
95
|
})
|
|
95
96
|
})
|
|
96
97
|
|
|
97
98
|
it('Should move focus down', async () => {
|
|
98
|
-
await usingAsync(
|
|
99
|
+
await usingAsync(createInjector(), async (i) => {
|
|
99
100
|
const top = createButton('top', { left: 0, top: 0, width: 50, height: 50 })
|
|
100
101
|
const bottom = createButton('bottom', { left: 0, top: 100, width: 50, height: 50 })
|
|
101
102
|
document.body.append(top, bottom)
|
|
102
103
|
|
|
103
104
|
top.focus()
|
|
104
105
|
|
|
105
|
-
const s = i.
|
|
106
|
+
const s = i.get(SpatialNavigationService)
|
|
106
107
|
s.moveFocus('down')
|
|
107
108
|
expect(document.activeElement).toBe(bottom)
|
|
108
109
|
})
|
|
109
110
|
})
|
|
110
111
|
|
|
111
112
|
it('Should move focus up', async () => {
|
|
112
|
-
await usingAsync(
|
|
113
|
+
await usingAsync(createInjector(), async (i) => {
|
|
113
114
|
const top = createButton('top', { left: 0, top: 0, width: 50, height: 50 })
|
|
114
115
|
const bottom = createButton('bottom', { left: 0, top: 100, width: 50, height: 50 })
|
|
115
116
|
document.body.append(top, bottom)
|
|
116
117
|
|
|
117
118
|
bottom.focus()
|
|
118
119
|
|
|
119
|
-
const s = i.
|
|
120
|
+
const s = i.get(SpatialNavigationService)
|
|
120
121
|
s.moveFocus('up')
|
|
121
122
|
expect(document.activeElement).toBe(top)
|
|
122
123
|
})
|
|
123
124
|
})
|
|
124
125
|
|
|
125
126
|
it('Should select nearest element by Euclidean distance', async () => {
|
|
126
|
-
await usingAsync(
|
|
127
|
+
await usingAsync(createInjector(), async (i) => {
|
|
127
128
|
const origin = createButton('origin', { left: 0, top: 0, width: 50, height: 50 })
|
|
128
129
|
const near = createButton('near', { left: 100, top: 10, width: 50, height: 50 })
|
|
129
130
|
const far = createButton('far', { left: 300, top: 10, width: 50, height: 50 })
|
|
@@ -131,34 +132,34 @@ describe('SpatialNavigationService', () => {
|
|
|
131
132
|
|
|
132
133
|
origin.focus()
|
|
133
134
|
|
|
134
|
-
const s = i.
|
|
135
|
+
const s = i.get(SpatialNavigationService)
|
|
135
136
|
s.moveFocus('right')
|
|
136
137
|
expect(document.activeElement).toBe(near)
|
|
137
138
|
})
|
|
138
139
|
})
|
|
139
140
|
|
|
140
141
|
it('Should be a no-op when no candidate exists in the direction', async () => {
|
|
141
|
-
await usingAsync(
|
|
142
|
+
await usingAsync(createInjector(), async (i) => {
|
|
142
143
|
const only = createButton('only', { left: 0, top: 0, width: 50, height: 50 })
|
|
143
144
|
document.body.append(only)
|
|
144
145
|
|
|
145
146
|
only.focus()
|
|
146
147
|
|
|
147
|
-
const s = i.
|
|
148
|
+
const s = i.get(SpatialNavigationService)
|
|
148
149
|
s.moveFocus('right')
|
|
149
150
|
expect(document.activeElement).toBe(only)
|
|
150
151
|
})
|
|
151
152
|
})
|
|
152
153
|
|
|
153
154
|
it('Should call scrollIntoView on the target element', async () => {
|
|
154
|
-
await usingAsync(
|
|
155
|
+
await usingAsync(createInjector(), async (i) => {
|
|
155
156
|
const left = createButton('left', { left: 0, top: 0, width: 50, height: 50 })
|
|
156
157
|
const right = createButton('right', { left: 100, top: 0, width: 50, height: 50 })
|
|
157
158
|
document.body.append(left, right)
|
|
158
159
|
|
|
159
160
|
left.focus()
|
|
160
161
|
|
|
161
|
-
const s = i.
|
|
162
|
+
const s = i.get(SpatialNavigationService)
|
|
162
163
|
s.moveFocus('right')
|
|
163
164
|
expect(right.scrollIntoView).toHaveBeenCalledWith({ block: 'nearest', inline: 'nearest' })
|
|
164
165
|
})
|
|
@@ -167,33 +168,33 @@ describe('SpatialNavigationService', () => {
|
|
|
167
168
|
|
|
168
169
|
describe('initial focus', () => {
|
|
169
170
|
it('Should focus first element when no element is focused', async () => {
|
|
170
|
-
await usingAsync(
|
|
171
|
+
await usingAsync(createInjector(), async (i) => {
|
|
171
172
|
const btn = createButton('first', { left: 0, top: 0, width: 50, height: 50 })
|
|
172
173
|
document.body.append(btn)
|
|
173
174
|
|
|
174
|
-
const s = i.
|
|
175
|
+
const s = i.get(SpatialNavigationService)
|
|
175
176
|
s.moveFocus('down')
|
|
176
177
|
expect(document.activeElement).toBe(btn)
|
|
177
178
|
})
|
|
178
179
|
})
|
|
179
180
|
|
|
180
181
|
it('Should focus first element in first section when no element is focused', async () => {
|
|
181
|
-
await usingAsync(
|
|
182
|
+
await usingAsync(createInjector(), async (i) => {
|
|
182
183
|
const section = document.createElement('div')
|
|
183
184
|
section.setAttribute('data-nav-section', 'main')
|
|
184
185
|
const btn = createButton('first', { left: 0, top: 0, width: 50, height: 50 })
|
|
185
186
|
section.append(btn)
|
|
186
187
|
document.body.append(section)
|
|
187
188
|
|
|
188
|
-
const s = i.
|
|
189
|
+
const s = i.get(SpatialNavigationService)
|
|
189
190
|
s.moveFocus('down')
|
|
190
191
|
expect(document.activeElement).toBe(btn)
|
|
191
192
|
})
|
|
192
193
|
})
|
|
193
194
|
|
|
194
195
|
it('Should be a no-op when there are no focusable elements', async () => {
|
|
195
|
-
await usingAsync(
|
|
196
|
-
const s = i.
|
|
196
|
+
await usingAsync(createInjector(), async (i) => {
|
|
197
|
+
const s = i.get(SpatialNavigationService)
|
|
197
198
|
s.moveFocus('down')
|
|
198
199
|
expect(document.activeElement).toBe(document.body)
|
|
199
200
|
})
|
|
@@ -202,13 +203,13 @@ describe('SpatialNavigationService', () => {
|
|
|
202
203
|
|
|
203
204
|
describe('keydown event handling', () => {
|
|
204
205
|
it('Should move focus on arrow key press', async () => {
|
|
205
|
-
await usingAsync(
|
|
206
|
+
await usingAsync(createInjector(), async (i) => {
|
|
206
207
|
const left = createButton('left', { left: 0, top: 0, width: 50, height: 50 })
|
|
207
208
|
const right = createButton('right', { left: 100, top: 0, width: 50, height: 50 })
|
|
208
209
|
document.body.append(left, right)
|
|
209
210
|
|
|
210
211
|
left.focus()
|
|
211
|
-
i.
|
|
212
|
+
i.get(SpatialNavigationService)
|
|
212
213
|
|
|
213
214
|
pressKey('ArrowRight')
|
|
214
215
|
expect(document.activeElement).toBe(right)
|
|
@@ -216,14 +217,14 @@ describe('SpatialNavigationService', () => {
|
|
|
216
217
|
})
|
|
217
218
|
|
|
218
219
|
it('Should activate focused element on Enter', async () => {
|
|
219
|
-
await usingAsync(
|
|
220
|
+
await usingAsync(createInjector(), async (i) => {
|
|
220
221
|
const btn = createButton('btn', { left: 0, top: 0, width: 50, height: 50 })
|
|
221
222
|
const clickHandler = vi.fn()
|
|
222
223
|
btn.addEventListener('click', clickHandler)
|
|
223
224
|
document.body.append(btn)
|
|
224
225
|
|
|
225
226
|
btn.focus()
|
|
226
|
-
i.
|
|
227
|
+
i.get(SpatialNavigationService)
|
|
227
228
|
|
|
228
229
|
pressKey('Enter')
|
|
229
230
|
expect(clickHandler).toHaveBeenCalledTimes(1)
|
|
@@ -231,13 +232,13 @@ describe('SpatialNavigationService', () => {
|
|
|
231
232
|
})
|
|
232
233
|
|
|
233
234
|
it('Should not handle events when disabled', async () => {
|
|
234
|
-
await usingAsync(
|
|
235
|
+
await usingAsync(createInjector(), async (i) => {
|
|
235
236
|
const left = createButton('left', { left: 0, top: 0, width: 50, height: 50 })
|
|
236
237
|
const right = createButton('right', { left: 100, top: 0, width: 50, height: 50 })
|
|
237
238
|
document.body.append(left, right)
|
|
238
239
|
|
|
239
240
|
left.focus()
|
|
240
|
-
const s = i.
|
|
241
|
+
const s = i.get(SpatialNavigationService)
|
|
241
242
|
s.enabled.setValue(false)
|
|
242
243
|
|
|
243
244
|
pressKey('ArrowRight')
|
|
@@ -246,13 +247,13 @@ describe('SpatialNavigationService', () => {
|
|
|
246
247
|
})
|
|
247
248
|
|
|
248
249
|
it('Should skip events that are already defaultPrevented', async () => {
|
|
249
|
-
await usingAsync(
|
|
250
|
+
await usingAsync(createInjector(), async (i) => {
|
|
250
251
|
const left = createButton('left', { left: 0, top: 0, width: 50, height: 50 })
|
|
251
252
|
const right = createButton('right', { left: 100, top: 0, width: 50, height: 50 })
|
|
252
253
|
document.body.append(left, right)
|
|
253
254
|
|
|
254
255
|
left.focus()
|
|
255
|
-
i.
|
|
256
|
+
i.get(SpatialNavigationService)
|
|
256
257
|
|
|
257
258
|
const preventer = (ev: Event) => ev.preventDefault()
|
|
258
259
|
window.addEventListener('keydown', preventer, { capture: true })
|
|
@@ -269,7 +270,7 @@ describe('SpatialNavigationService', () => {
|
|
|
269
270
|
|
|
270
271
|
describe('input passthrough', () => {
|
|
271
272
|
it('Should not intercept arrow keys on text input when cursor is mid-text', async () => {
|
|
272
|
-
await usingAsync(
|
|
273
|
+
await usingAsync(createInjector(), async (i) => {
|
|
273
274
|
const input = document.createElement('input')
|
|
274
275
|
input.type = 'text'
|
|
275
276
|
input.value = 'hello'
|
|
@@ -279,7 +280,7 @@ describe('SpatialNavigationService', () => {
|
|
|
279
280
|
|
|
280
281
|
input.focus()
|
|
281
282
|
input.setSelectionRange(2, 2)
|
|
282
|
-
i.
|
|
283
|
+
i.get(SpatialNavigationService)
|
|
283
284
|
|
|
284
285
|
pressKey('ArrowRight')
|
|
285
286
|
expect(document.activeElement).toBe(input)
|
|
@@ -287,7 +288,7 @@ describe('SpatialNavigationService', () => {
|
|
|
287
288
|
})
|
|
288
289
|
|
|
289
290
|
it('Should escape text input with ArrowRight when cursor is at end', async () => {
|
|
290
|
-
await usingAsync(
|
|
291
|
+
await usingAsync(createInjector(), async (i) => {
|
|
291
292
|
const input = document.createElement('input')
|
|
292
293
|
input.type = 'text'
|
|
293
294
|
input.value = 'hello'
|
|
@@ -298,7 +299,7 @@ describe('SpatialNavigationService', () => {
|
|
|
298
299
|
|
|
299
300
|
input.focus()
|
|
300
301
|
input.setSelectionRange(5, 5)
|
|
301
|
-
i.
|
|
302
|
+
i.get(SpatialNavigationService)
|
|
302
303
|
|
|
303
304
|
pressKey('ArrowRight')
|
|
304
305
|
expect(document.activeElement).toBe(btn)
|
|
@@ -306,7 +307,7 @@ describe('SpatialNavigationService', () => {
|
|
|
306
307
|
})
|
|
307
308
|
|
|
308
309
|
it('Should escape text input with ArrowLeft when cursor is at start', async () => {
|
|
309
|
-
await usingAsync(
|
|
310
|
+
await usingAsync(createInjector(), async (i) => {
|
|
310
311
|
const input = document.createElement('input')
|
|
311
312
|
input.type = 'text'
|
|
312
313
|
input.value = 'hello'
|
|
@@ -317,7 +318,7 @@ describe('SpatialNavigationService', () => {
|
|
|
317
318
|
|
|
318
319
|
input.focus()
|
|
319
320
|
input.setSelectionRange(0, 0)
|
|
320
|
-
i.
|
|
321
|
+
i.get(SpatialNavigationService)
|
|
321
322
|
|
|
322
323
|
pressKey('ArrowLeft')
|
|
323
324
|
expect(document.activeElement).toBe(btn)
|
|
@@ -325,7 +326,7 @@ describe('SpatialNavigationService', () => {
|
|
|
325
326
|
})
|
|
326
327
|
|
|
327
328
|
it('Should not intercept arrow keys on textarea when cursor is mid-text', async () => {
|
|
328
|
-
await usingAsync(
|
|
329
|
+
await usingAsync(createInjector(), async (i) => {
|
|
329
330
|
const textarea = document.createElement('textarea')
|
|
330
331
|
textarea.value = 'line1\nline2'
|
|
331
332
|
mockRect(textarea, { left: 0, top: 0, width: 200, height: 100 })
|
|
@@ -334,7 +335,7 @@ describe('SpatialNavigationService', () => {
|
|
|
334
335
|
|
|
335
336
|
textarea.focus()
|
|
336
337
|
textarea.setSelectionRange(3, 3)
|
|
337
|
-
i.
|
|
338
|
+
i.get(SpatialNavigationService)
|
|
338
339
|
|
|
339
340
|
pressKey('ArrowDown')
|
|
340
341
|
expect(document.activeElement).toBe(textarea)
|
|
@@ -342,14 +343,14 @@ describe('SpatialNavigationService', () => {
|
|
|
342
343
|
})
|
|
343
344
|
|
|
344
345
|
it('Should not intercept arrow keys on select', async () => {
|
|
345
|
-
await usingAsync(
|
|
346
|
+
await usingAsync(createInjector(), async (i) => {
|
|
346
347
|
const select = document.createElement('select')
|
|
347
348
|
mockRect(select, { left: 0, top: 0, width: 200, height: 30 })
|
|
348
349
|
const btn = createButton('btn', { left: 0, top: 100, width: 50, height: 50 })
|
|
349
350
|
document.body.append(select, btn)
|
|
350
351
|
|
|
351
352
|
select.focus()
|
|
352
|
-
i.
|
|
353
|
+
i.get(SpatialNavigationService)
|
|
353
354
|
|
|
354
355
|
pressKey('ArrowDown')
|
|
355
356
|
expect(document.activeElement).toBe(select)
|
|
@@ -357,7 +358,7 @@ describe('SpatialNavigationService', () => {
|
|
|
357
358
|
})
|
|
358
359
|
|
|
359
360
|
it('Should not intercept arrow keys on contenteditable', async () => {
|
|
360
|
-
await usingAsync(
|
|
361
|
+
await usingAsync(createInjector(), async (i) => {
|
|
361
362
|
const div = document.createElement('div')
|
|
362
363
|
div.contentEditable = 'true'
|
|
363
364
|
div.tabIndex = 0
|
|
@@ -366,7 +367,7 @@ describe('SpatialNavigationService', () => {
|
|
|
366
367
|
document.body.append(div, btn)
|
|
367
368
|
|
|
368
369
|
div.focus()
|
|
369
|
-
i.
|
|
370
|
+
i.get(SpatialNavigationService)
|
|
370
371
|
|
|
371
372
|
pressKey('ArrowRight')
|
|
372
373
|
expect(document.activeElement).toBe(div)
|
|
@@ -374,7 +375,7 @@ describe('SpatialNavigationService', () => {
|
|
|
374
375
|
})
|
|
375
376
|
|
|
376
377
|
it('Should not intercept arrow keys on children of contenteditable', async () => {
|
|
377
|
-
await usingAsync(
|
|
378
|
+
await usingAsync(createInjector(), async (i) => {
|
|
378
379
|
const div = document.createElement('div')
|
|
379
380
|
div.contentEditable = 'true'
|
|
380
381
|
div.tabIndex = 0
|
|
@@ -390,7 +391,7 @@ describe('SpatialNavigationService', () => {
|
|
|
390
391
|
document.body.append(div, btn)
|
|
391
392
|
|
|
392
393
|
span.focus()
|
|
393
|
-
i.
|
|
394
|
+
i.get(SpatialNavigationService)
|
|
394
395
|
|
|
395
396
|
pressKey('ArrowRight')
|
|
396
397
|
expect(document.activeElement).toBe(span)
|
|
@@ -398,7 +399,7 @@ describe('SpatialNavigationService', () => {
|
|
|
398
399
|
})
|
|
399
400
|
|
|
400
401
|
it('Should not intercept Enter on children of contenteditable', async () => {
|
|
401
|
-
await usingAsync(
|
|
402
|
+
await usingAsync(createInjector(), async (i) => {
|
|
402
403
|
const div = document.createElement('div')
|
|
403
404
|
div.contentEditable = 'true'
|
|
404
405
|
div.tabIndex = 0
|
|
@@ -414,7 +415,7 @@ describe('SpatialNavigationService', () => {
|
|
|
414
415
|
document.body.append(div)
|
|
415
416
|
|
|
416
417
|
span.focus()
|
|
417
|
-
i.
|
|
418
|
+
i.get(SpatialNavigationService)
|
|
418
419
|
|
|
419
420
|
pressKey('Enter')
|
|
420
421
|
expect(clickHandler).not.toHaveBeenCalled()
|
|
@@ -422,7 +423,7 @@ describe('SpatialNavigationService', () => {
|
|
|
422
423
|
})
|
|
423
424
|
|
|
424
425
|
it('Should intercept arrow keys on button-type input', async () => {
|
|
425
|
-
await usingAsync(
|
|
426
|
+
await usingAsync(createInjector(), async (i) => {
|
|
426
427
|
const input = document.createElement('input')
|
|
427
428
|
input.type = 'button'
|
|
428
429
|
mockRect(input, { left: 0, top: 0, width: 50, height: 30 })
|
|
@@ -431,7 +432,7 @@ describe('SpatialNavigationService', () => {
|
|
|
431
432
|
document.body.append(input, btn)
|
|
432
433
|
|
|
433
434
|
input.focus()
|
|
434
|
-
i.
|
|
435
|
+
i.get(SpatialNavigationService)
|
|
435
436
|
|
|
436
437
|
pressKey('ArrowRight')
|
|
437
438
|
expect(document.activeElement).toBe(btn)
|
|
@@ -439,7 +440,7 @@ describe('SpatialNavigationService', () => {
|
|
|
439
440
|
})
|
|
440
441
|
|
|
441
442
|
it('Should not intercept Left/Right on range input (slider adjustment)', async () => {
|
|
442
|
-
await usingAsync(
|
|
443
|
+
await usingAsync(createInjector(), async (i) => {
|
|
443
444
|
const input = document.createElement('input')
|
|
444
445
|
input.type = 'range'
|
|
445
446
|
mockRect(input, { left: 0, top: 0, width: 200, height: 30 })
|
|
@@ -447,7 +448,7 @@ describe('SpatialNavigationService', () => {
|
|
|
447
448
|
document.body.append(input, btn)
|
|
448
449
|
|
|
449
450
|
input.focus()
|
|
450
|
-
i.
|
|
451
|
+
i.get(SpatialNavigationService)
|
|
451
452
|
|
|
452
453
|
pressKey('ArrowLeft')
|
|
453
454
|
expect(document.activeElement).toBe(input)
|
|
@@ -458,7 +459,7 @@ describe('SpatialNavigationService', () => {
|
|
|
458
459
|
})
|
|
459
460
|
|
|
460
461
|
it('Should intercept Up/Down on range input for spatial navigation', async () => {
|
|
461
|
-
await usingAsync(
|
|
462
|
+
await usingAsync(createInjector(), async (i) => {
|
|
462
463
|
const input = document.createElement('input')
|
|
463
464
|
input.type = 'range'
|
|
464
465
|
mockRect(input, { left: 0, top: 0, width: 200, height: 30 })
|
|
@@ -467,7 +468,7 @@ describe('SpatialNavigationService', () => {
|
|
|
467
468
|
document.body.append(input, btn)
|
|
468
469
|
|
|
469
470
|
input.focus()
|
|
470
|
-
i.
|
|
471
|
+
i.get(SpatialNavigationService)
|
|
471
472
|
|
|
472
473
|
pressKey('ArrowDown')
|
|
473
474
|
expect(document.activeElement).toBe(btn)
|
|
@@ -475,7 +476,7 @@ describe('SpatialNavigationService', () => {
|
|
|
475
476
|
})
|
|
476
477
|
|
|
477
478
|
it('Should intercept arrow keys on color input', async () => {
|
|
478
|
-
await usingAsync(
|
|
479
|
+
await usingAsync(createInjector(), async (i) => {
|
|
479
480
|
const input = document.createElement('input')
|
|
480
481
|
input.type = 'color'
|
|
481
482
|
mockRect(input, { left: 0, top: 0, width: 50, height: 50 })
|
|
@@ -484,7 +485,7 @@ describe('SpatialNavigationService', () => {
|
|
|
484
485
|
document.body.append(input, btn)
|
|
485
486
|
|
|
486
487
|
input.focus()
|
|
487
|
-
i.
|
|
488
|
+
i.get(SpatialNavigationService)
|
|
488
489
|
|
|
489
490
|
pressKey('ArrowRight')
|
|
490
491
|
expect(document.activeElement).toBe(btn)
|
|
@@ -492,7 +493,7 @@ describe('SpatialNavigationService', () => {
|
|
|
492
493
|
})
|
|
493
494
|
|
|
494
495
|
it('Should intercept arrow keys on file input', async () => {
|
|
495
|
-
await usingAsync(
|
|
496
|
+
await usingAsync(createInjector(), async (i) => {
|
|
496
497
|
const input = document.createElement('input')
|
|
497
498
|
input.type = 'file'
|
|
498
499
|
mockRect(input, { left: 0, top: 0, width: 200, height: 30 })
|
|
@@ -501,7 +502,7 @@ describe('SpatialNavigationService', () => {
|
|
|
501
502
|
document.body.append(input, btn)
|
|
502
503
|
|
|
503
504
|
input.focus()
|
|
504
|
-
i.
|
|
505
|
+
i.get(SpatialNavigationService)
|
|
505
506
|
|
|
506
507
|
pressKey('ArrowRight')
|
|
507
508
|
expect(document.activeElement).toBe(btn)
|
|
@@ -509,7 +510,7 @@ describe('SpatialNavigationService', () => {
|
|
|
509
510
|
})
|
|
510
511
|
|
|
511
512
|
it('Should not intercept arrow keys on radio input', async () => {
|
|
512
|
-
await usingAsync(
|
|
513
|
+
await usingAsync(createInjector(), async (i) => {
|
|
513
514
|
const radio = document.createElement('input')
|
|
514
515
|
radio.type = 'radio'
|
|
515
516
|
radio.name = 'group'
|
|
@@ -518,7 +519,7 @@ describe('SpatialNavigationService', () => {
|
|
|
518
519
|
document.body.append(radio, btn)
|
|
519
520
|
|
|
520
521
|
radio.focus()
|
|
521
|
-
i.
|
|
522
|
+
i.get(SpatialNavigationService)
|
|
522
523
|
|
|
523
524
|
pressKey('ArrowDown')
|
|
524
525
|
expect(document.activeElement).toBe(radio)
|
|
@@ -529,7 +530,7 @@ describe('SpatialNavigationService', () => {
|
|
|
529
530
|
})
|
|
530
531
|
|
|
531
532
|
it('Should not intercept Up/Down on number input (increment/decrement)', async () => {
|
|
532
|
-
await usingAsync(
|
|
533
|
+
await usingAsync(createInjector(), async (i) => {
|
|
533
534
|
const input = document.createElement('input')
|
|
534
535
|
input.type = 'number'
|
|
535
536
|
input.value = '5'
|
|
@@ -538,7 +539,7 @@ describe('SpatialNavigationService', () => {
|
|
|
538
539
|
document.body.append(input, btn)
|
|
539
540
|
|
|
540
541
|
input.focus()
|
|
541
|
-
i.
|
|
542
|
+
i.get(SpatialNavigationService)
|
|
542
543
|
|
|
543
544
|
pressKey('ArrowUp')
|
|
544
545
|
expect(document.activeElement).toBe(input)
|
|
@@ -549,7 +550,7 @@ describe('SpatialNavigationService', () => {
|
|
|
549
550
|
})
|
|
550
551
|
|
|
551
552
|
it('Should intercept Left/Right on number input for spatial navigation', async () => {
|
|
552
|
-
await usingAsync(
|
|
553
|
+
await usingAsync(createInjector(), async (i) => {
|
|
553
554
|
const input = document.createElement('input')
|
|
554
555
|
input.type = 'number'
|
|
555
556
|
input.value = '5'
|
|
@@ -560,7 +561,7 @@ describe('SpatialNavigationService', () => {
|
|
|
560
561
|
document.body.append(btnLeft, input, btnRight)
|
|
561
562
|
|
|
562
563
|
input.focus()
|
|
563
|
-
i.
|
|
564
|
+
i.get(SpatialNavigationService)
|
|
564
565
|
|
|
565
566
|
pressKey('ArrowRight')
|
|
566
567
|
expect(document.activeElement).toBe(btnRight)
|
|
@@ -568,7 +569,7 @@ describe('SpatialNavigationService', () => {
|
|
|
568
569
|
})
|
|
569
570
|
|
|
570
571
|
it('Should not intercept arrow keys on date input', async () => {
|
|
571
|
-
await usingAsync(
|
|
572
|
+
await usingAsync(createInjector(), async (i) => {
|
|
572
573
|
const input = document.createElement('input')
|
|
573
574
|
input.type = 'date'
|
|
574
575
|
mockRect(input, { left: 0, top: 0, width: 200, height: 30 })
|
|
@@ -576,7 +577,7 @@ describe('SpatialNavigationService', () => {
|
|
|
576
577
|
document.body.append(input, btn)
|
|
577
578
|
|
|
578
579
|
input.focus()
|
|
579
|
-
i.
|
|
580
|
+
i.get(SpatialNavigationService)
|
|
580
581
|
|
|
581
582
|
pressKey('ArrowRight')
|
|
582
583
|
expect(document.activeElement).toBe(input)
|
|
@@ -587,7 +588,7 @@ describe('SpatialNavigationService', () => {
|
|
|
587
588
|
})
|
|
588
589
|
|
|
589
590
|
it('Should not intercept arrow keys on time input', async () => {
|
|
590
|
-
await usingAsync(
|
|
591
|
+
await usingAsync(createInjector(), async (i) => {
|
|
591
592
|
const input = document.createElement('input')
|
|
592
593
|
input.type = 'time'
|
|
593
594
|
mockRect(input, { left: 0, top: 0, width: 200, height: 30 })
|
|
@@ -595,7 +596,7 @@ describe('SpatialNavigationService', () => {
|
|
|
595
596
|
document.body.append(input, btn)
|
|
596
597
|
|
|
597
598
|
input.focus()
|
|
598
|
-
i.
|
|
599
|
+
i.get(SpatialNavigationService)
|
|
599
600
|
|
|
600
601
|
pressKey('ArrowDown')
|
|
601
602
|
expect(document.activeElement).toBe(input)
|
|
@@ -606,7 +607,7 @@ describe('SpatialNavigationService', () => {
|
|
|
606
607
|
})
|
|
607
608
|
|
|
608
609
|
it('Should escape empty text input on any arrow key', async () => {
|
|
609
|
-
await usingAsync(
|
|
610
|
+
await usingAsync(createInjector(), async (i) => {
|
|
610
611
|
const input = document.createElement('input')
|
|
611
612
|
input.type = 'text'
|
|
612
613
|
mockRect(input, { left: 0, top: 0, width: 200, height: 30 })
|
|
@@ -615,7 +616,7 @@ describe('SpatialNavigationService', () => {
|
|
|
615
616
|
document.body.append(input, btn)
|
|
616
617
|
|
|
617
618
|
input.focus()
|
|
618
|
-
i.
|
|
619
|
+
i.get(SpatialNavigationService)
|
|
619
620
|
|
|
620
621
|
pressKey('ArrowRight')
|
|
621
622
|
expect(document.activeElement).toBe(btn)
|
|
@@ -623,7 +624,7 @@ describe('SpatialNavigationService', () => {
|
|
|
623
624
|
})
|
|
624
625
|
|
|
625
626
|
it('Should not intercept Enter on text input', async () => {
|
|
626
|
-
await usingAsync(
|
|
627
|
+
await usingAsync(createInjector(), async (i) => {
|
|
627
628
|
const input = document.createElement('input')
|
|
628
629
|
input.type = 'text'
|
|
629
630
|
mockRect(input, { left: 0, top: 0, width: 200, height: 30 })
|
|
@@ -632,7 +633,7 @@ describe('SpatialNavigationService', () => {
|
|
|
632
633
|
document.body.append(input)
|
|
633
634
|
|
|
634
635
|
input.focus()
|
|
635
|
-
i.
|
|
636
|
+
i.get(SpatialNavigationService)
|
|
636
637
|
|
|
637
638
|
pressKey('Enter')
|
|
638
639
|
expect(clickHandler).not.toHaveBeenCalled()
|
|
@@ -640,7 +641,7 @@ describe('SpatialNavigationService', () => {
|
|
|
640
641
|
})
|
|
641
642
|
|
|
642
643
|
it('Should not intercept Enter on textarea', async () => {
|
|
643
|
-
await usingAsync(
|
|
644
|
+
await usingAsync(createInjector(), async (i) => {
|
|
644
645
|
const textarea = document.createElement('textarea')
|
|
645
646
|
textarea.value = 'hello'
|
|
646
647
|
mockRect(textarea, { left: 0, top: 0, width: 200, height: 100 })
|
|
@@ -649,7 +650,7 @@ describe('SpatialNavigationService', () => {
|
|
|
649
650
|
document.body.append(textarea)
|
|
650
651
|
|
|
651
652
|
textarea.focus()
|
|
652
|
-
i.
|
|
653
|
+
i.get(SpatialNavigationService)
|
|
653
654
|
|
|
654
655
|
pressKey('Enter')
|
|
655
656
|
expect(clickHandler).not.toHaveBeenCalled()
|
|
@@ -657,7 +658,7 @@ describe('SpatialNavigationService', () => {
|
|
|
657
658
|
})
|
|
658
659
|
|
|
659
660
|
it('Should activate Enter on button-type input', async () => {
|
|
660
|
-
await usingAsync(
|
|
661
|
+
await usingAsync(createInjector(), async (i) => {
|
|
661
662
|
const input = document.createElement('input')
|
|
662
663
|
input.type = 'button'
|
|
663
664
|
mockRect(input, { left: 0, top: 0, width: 50, height: 30 })
|
|
@@ -666,7 +667,7 @@ describe('SpatialNavigationService', () => {
|
|
|
666
667
|
document.body.append(input)
|
|
667
668
|
|
|
668
669
|
input.focus()
|
|
669
|
-
i.
|
|
670
|
+
i.get(SpatialNavigationService)
|
|
670
671
|
|
|
671
672
|
pressKey('Enter')
|
|
672
673
|
expect(clickHandler).toHaveBeenCalledTimes(1)
|
|
@@ -676,7 +677,7 @@ describe('SpatialNavigationService', () => {
|
|
|
676
677
|
|
|
677
678
|
describe('data-spatial-nav-passthrough', () => {
|
|
678
679
|
it('Should not intercept arrow keys inside a passthrough container', async () => {
|
|
679
|
-
await usingAsync(
|
|
680
|
+
await usingAsync(createInjector(), async (i) => {
|
|
680
681
|
const container = document.createElement('div')
|
|
681
682
|
container.setAttribute('data-spatial-nav-passthrough', '')
|
|
682
683
|
const inner = document.createElement('input')
|
|
@@ -688,7 +689,7 @@ describe('SpatialNavigationService', () => {
|
|
|
688
689
|
document.body.append(container, btn)
|
|
689
690
|
|
|
690
691
|
inner.focus()
|
|
691
|
-
i.
|
|
692
|
+
i.get(SpatialNavigationService)
|
|
692
693
|
|
|
693
694
|
pressKey('ArrowRight')
|
|
694
695
|
expect(document.activeElement).toBe(inner)
|
|
@@ -696,7 +697,7 @@ describe('SpatialNavigationService', () => {
|
|
|
696
697
|
})
|
|
697
698
|
|
|
698
699
|
it('Should not intercept Enter inside a passthrough container', async () => {
|
|
699
|
-
await usingAsync(
|
|
700
|
+
await usingAsync(createInjector(), async (i) => {
|
|
700
701
|
const container = document.createElement('div')
|
|
701
702
|
container.setAttribute('data-spatial-nav-passthrough', '')
|
|
702
703
|
const inner = createButton('inner', { left: 0, top: 0, width: 50, height: 50 })
|
|
@@ -706,7 +707,7 @@ describe('SpatialNavigationService', () => {
|
|
|
706
707
|
document.body.append(container)
|
|
707
708
|
|
|
708
709
|
inner.focus()
|
|
709
|
-
i.
|
|
710
|
+
i.get(SpatialNavigationService)
|
|
710
711
|
|
|
711
712
|
pressKey('Enter')
|
|
712
713
|
expect(clickHandler).not.toHaveBeenCalled()
|
|
@@ -714,7 +715,7 @@ describe('SpatialNavigationService', () => {
|
|
|
714
715
|
})
|
|
715
716
|
|
|
716
717
|
it('Should still intercept keys outside a passthrough container', async () => {
|
|
717
|
-
await usingAsync(
|
|
718
|
+
await usingAsync(createInjector(), async (i) => {
|
|
718
719
|
const container = document.createElement('div')
|
|
719
720
|
container.setAttribute('data-spatial-nav-passthrough', '')
|
|
720
721
|
const inner = document.createElement('input')
|
|
@@ -727,7 +728,7 @@ describe('SpatialNavigationService', () => {
|
|
|
727
728
|
document.body.append(left, right, container)
|
|
728
729
|
|
|
729
730
|
left.focus()
|
|
730
|
-
i.
|
|
731
|
+
i.get(SpatialNavigationService)
|
|
731
732
|
|
|
732
733
|
pressKey('ArrowRight')
|
|
733
734
|
expect(document.activeElement).toBe(right)
|
|
@@ -737,7 +738,7 @@ describe('SpatialNavigationService', () => {
|
|
|
737
738
|
|
|
738
739
|
describe('section navigation', () => {
|
|
739
740
|
it('Should scope navigation within the active section', async () => {
|
|
740
|
-
await usingAsync(
|
|
741
|
+
await usingAsync(createInjector(), async (i) => {
|
|
741
742
|
const section1 = document.createElement('div')
|
|
742
743
|
section1.setAttribute('data-nav-section', 'sidebar')
|
|
743
744
|
mockRect(section1, { left: 0, top: 0, width: 200, height: 400 })
|
|
@@ -755,14 +756,14 @@ describe('SpatialNavigationService', () => {
|
|
|
755
756
|
|
|
756
757
|
btn2.focus()
|
|
757
758
|
|
|
758
|
-
const s = i.
|
|
759
|
+
const s = i.get(SpatialNavigationService)
|
|
759
760
|
s.moveFocus('down')
|
|
760
761
|
expect(document.activeElement).toBe(btn3)
|
|
761
762
|
})
|
|
762
763
|
})
|
|
763
764
|
|
|
764
765
|
it('Should update activeSection when focus moves', async () => {
|
|
765
|
-
await usingAsync(
|
|
766
|
+
await usingAsync(createInjector(), async (i) => {
|
|
766
767
|
const section = document.createElement('div')
|
|
767
768
|
section.setAttribute('data-nav-section', 'main')
|
|
768
769
|
const btn = createButton('btn', { left: 0, top: 0, width: 50, height: 50 })
|
|
@@ -771,7 +772,7 @@ describe('SpatialNavigationService', () => {
|
|
|
771
772
|
|
|
772
773
|
btn.focus()
|
|
773
774
|
|
|
774
|
-
const s = i.
|
|
775
|
+
const s = i.get(SpatialNavigationService)
|
|
775
776
|
s.moveFocus('down')
|
|
776
777
|
expect(s.activeSection.getValue()).toBe('main')
|
|
777
778
|
})
|
|
@@ -780,7 +781,7 @@ describe('SpatialNavigationService', () => {
|
|
|
780
781
|
|
|
781
782
|
describe('cross-section navigation', () => {
|
|
782
783
|
it('Should navigate to adjacent section when no candidate in current section', async () => {
|
|
783
|
-
await usingAsync(
|
|
784
|
+
await usingAsync(createInjector(), async (i) => {
|
|
784
785
|
const section1 = document.createElement('div')
|
|
785
786
|
section1.setAttribute('data-nav-section', 'left')
|
|
786
787
|
mockRect(section1, { left: 0, top: 0, width: 200, height: 400 })
|
|
@@ -798,7 +799,7 @@ describe('SpatialNavigationService', () => {
|
|
|
798
799
|
|
|
799
800
|
btn1.focus()
|
|
800
801
|
|
|
801
|
-
const s = i.
|
|
802
|
+
const s = i.get(SpatialNavigationService)
|
|
802
803
|
s.moveFocus('right')
|
|
803
804
|
expect(document.activeElement).toBe(btn2)
|
|
804
805
|
expect(s.activeSection.getValue()).toBe('right')
|
|
@@ -806,7 +807,7 @@ describe('SpatialNavigationService', () => {
|
|
|
806
807
|
})
|
|
807
808
|
|
|
808
809
|
it('Should not navigate cross-section when disabled', async () => {
|
|
809
|
-
await usingAsync(
|
|
810
|
+
await usingAsync(createInjector(), async (i) => {
|
|
810
811
|
configureSpatialNavigation(i, { crossSectionNavigation: false })
|
|
811
812
|
|
|
812
813
|
const section1 = document.createElement('div')
|
|
@@ -825,7 +826,7 @@ describe('SpatialNavigationService', () => {
|
|
|
825
826
|
|
|
826
827
|
btn1.focus()
|
|
827
828
|
|
|
828
|
-
const s = i.
|
|
829
|
+
const s = i.get(SpatialNavigationService)
|
|
829
830
|
s.moveFocus('right')
|
|
830
831
|
expect(document.activeElement).toBe(btn1)
|
|
831
832
|
})
|
|
@@ -834,7 +835,7 @@ describe('SpatialNavigationService', () => {
|
|
|
834
835
|
|
|
835
836
|
describe('focus memory', () => {
|
|
836
837
|
it('Should remember and restore focus when returning to a section', async () => {
|
|
837
|
-
await usingAsync(
|
|
838
|
+
await usingAsync(createInjector(), async (i) => {
|
|
838
839
|
const section1 = document.createElement('div')
|
|
839
840
|
section1.setAttribute('data-nav-section', 'left')
|
|
840
841
|
mockRect(section1, { left: 0, top: 0, width: 200, height: 400 })
|
|
@@ -853,7 +854,7 @@ describe('SpatialNavigationService', () => {
|
|
|
853
854
|
|
|
854
855
|
btn1b.focus()
|
|
855
856
|
|
|
856
|
-
const s = i.
|
|
857
|
+
const s = i.get(SpatialNavigationService)
|
|
857
858
|
|
|
858
859
|
// Navigate away from section1
|
|
859
860
|
s.moveFocus('right')
|
|
@@ -867,7 +868,7 @@ describe('SpatialNavigationService', () => {
|
|
|
867
868
|
})
|
|
868
869
|
|
|
869
870
|
it('Should fall back to nearest element when remembered element is removed', async () => {
|
|
870
|
-
await usingAsync(
|
|
871
|
+
await usingAsync(createInjector(), async (i) => {
|
|
871
872
|
const section1 = document.createElement('div')
|
|
872
873
|
section1.setAttribute('data-nav-section', 'left')
|
|
873
874
|
mockRect(section1, { left: 0, top: 0, width: 200, height: 400 })
|
|
@@ -887,7 +888,7 @@ describe('SpatialNavigationService', () => {
|
|
|
887
888
|
|
|
888
889
|
// Focus btn2a, navigate to section1
|
|
889
890
|
btn2a.focus()
|
|
890
|
-
const s = i.
|
|
891
|
+
const s = i.get(SpatialNavigationService)
|
|
891
892
|
s.moveFocus('left')
|
|
892
893
|
|
|
893
894
|
// Remove btn2a from DOM
|
|
@@ -900,7 +901,7 @@ describe('SpatialNavigationService', () => {
|
|
|
900
901
|
})
|
|
901
902
|
|
|
902
903
|
it('Should fall back to nearest element when remembered element becomes disabled', async () => {
|
|
903
|
-
await usingAsync(
|
|
904
|
+
await usingAsync(createInjector(), async (i) => {
|
|
904
905
|
const section1 = document.createElement('div')
|
|
905
906
|
section1.setAttribute('data-nav-section', 'left')
|
|
906
907
|
mockRect(section1, { left: 0, top: 0, width: 200, height: 400 })
|
|
@@ -919,7 +920,7 @@ describe('SpatialNavigationService', () => {
|
|
|
919
920
|
document.body.append(section1, section2)
|
|
920
921
|
|
|
921
922
|
btn2a.focus()
|
|
922
|
-
const s = i.
|
|
923
|
+
const s = i.get(SpatialNavigationService)
|
|
923
924
|
s.moveFocus('left')
|
|
924
925
|
|
|
925
926
|
// Disable btn2a after navigating away
|
|
@@ -934,13 +935,13 @@ describe('SpatialNavigationService', () => {
|
|
|
934
935
|
|
|
935
936
|
describe('enabled toggle', () => {
|
|
936
937
|
it('Should stop handling keys when disabled', async () => {
|
|
937
|
-
await usingAsync(
|
|
938
|
+
await usingAsync(createInjector(), async (i) => {
|
|
938
939
|
const left = createButton('left', { left: 0, top: 0, width: 50, height: 50 })
|
|
939
940
|
const right = createButton('right', { left: 100, top: 0, width: 50, height: 50 })
|
|
940
941
|
document.body.append(left, right)
|
|
941
942
|
|
|
942
943
|
left.focus()
|
|
943
|
-
const s = i.
|
|
944
|
+
const s = i.get(SpatialNavigationService)
|
|
944
945
|
|
|
945
946
|
s.enabled.setValue(false)
|
|
946
947
|
pressKey('ArrowRight')
|
|
@@ -957,8 +958,8 @@ describe('SpatialNavigationService', () => {
|
|
|
957
958
|
it('Should remove keydown listener on dispose', async () => {
|
|
958
959
|
const removeEventListenerSpy = vi.spyOn(window, 'removeEventListener')
|
|
959
960
|
|
|
960
|
-
await usingAsync(
|
|
961
|
-
i.
|
|
961
|
+
await usingAsync(createInjector(), async (i) => {
|
|
962
|
+
i.get(SpatialNavigationService)
|
|
962
963
|
})
|
|
963
964
|
|
|
964
965
|
expect(removeEventListenerSpy).toHaveBeenCalledWith('keydown', expect.any(Function))
|
|
@@ -971,8 +972,8 @@ describe('SpatialNavigationService', () => {
|
|
|
971
972
|
|
|
972
973
|
left.focus()
|
|
973
974
|
|
|
974
|
-
await usingAsync(
|
|
975
|
-
i.
|
|
975
|
+
await usingAsync(createInjector(), async (i) => {
|
|
976
|
+
i.get(SpatialNavigationService)
|
|
976
977
|
})
|
|
977
978
|
|
|
978
979
|
pressKey('ArrowRight')
|
|
@@ -982,7 +983,7 @@ describe('SpatialNavigationService', () => {
|
|
|
982
983
|
|
|
983
984
|
describe('backspace and escape', () => {
|
|
984
985
|
it('Should call history.back() on Backspace when configured', async () => {
|
|
985
|
-
await usingAsync(
|
|
986
|
+
await usingAsync(createInjector(), async (i) => {
|
|
986
987
|
configureSpatialNavigation(i, { backspaceGoesBack: true })
|
|
987
988
|
|
|
988
989
|
const btn = createButton('btn', { left: 0, top: 0, width: 50, height: 50 })
|
|
@@ -990,7 +991,7 @@ describe('SpatialNavigationService', () => {
|
|
|
990
991
|
btn.focus()
|
|
991
992
|
|
|
992
993
|
const backSpy = vi.spyOn(history, 'back').mockImplementation(() => {})
|
|
993
|
-
i.
|
|
994
|
+
i.get(SpatialNavigationService)
|
|
994
995
|
|
|
995
996
|
pressKey('Backspace')
|
|
996
997
|
expect(backSpy).toHaveBeenCalledTimes(1)
|
|
@@ -998,13 +999,13 @@ describe('SpatialNavigationService', () => {
|
|
|
998
999
|
})
|
|
999
1000
|
|
|
1000
1001
|
it('Should not call history.back() on Backspace by default', async () => {
|
|
1001
|
-
await usingAsync(
|
|
1002
|
+
await usingAsync(createInjector(), async (i) => {
|
|
1002
1003
|
const btn = createButton('btn', { left: 0, top: 0, width: 50, height: 50 })
|
|
1003
1004
|
document.body.append(btn)
|
|
1004
1005
|
btn.focus()
|
|
1005
1006
|
|
|
1006
1007
|
const backSpy = vi.spyOn(history, 'back').mockImplementation(() => {})
|
|
1007
|
-
i.
|
|
1008
|
+
i.get(SpatialNavigationService)
|
|
1008
1009
|
|
|
1009
1010
|
pressKey('Backspace')
|
|
1010
1011
|
expect(backSpy).not.toHaveBeenCalled()
|
|
@@ -1012,7 +1013,7 @@ describe('SpatialNavigationService', () => {
|
|
|
1012
1013
|
})
|
|
1013
1014
|
|
|
1014
1015
|
it('Should not call history.back() on Backspace when focused on a text input', async () => {
|
|
1015
|
-
await usingAsync(
|
|
1016
|
+
await usingAsync(createInjector(), async (i) => {
|
|
1016
1017
|
configureSpatialNavigation(i, { backspaceGoesBack: true })
|
|
1017
1018
|
|
|
1018
1019
|
const input = document.createElement('input')
|
|
@@ -1023,7 +1024,7 @@ describe('SpatialNavigationService', () => {
|
|
|
1023
1024
|
input.focus()
|
|
1024
1025
|
|
|
1025
1026
|
const backSpy = vi.spyOn(history, 'back').mockImplementation(() => {})
|
|
1026
|
-
i.
|
|
1027
|
+
i.get(SpatialNavigationService)
|
|
1027
1028
|
|
|
1028
1029
|
pressKey('Backspace')
|
|
1029
1030
|
expect(backSpy).not.toHaveBeenCalled()
|
|
@@ -1031,7 +1032,7 @@ describe('SpatialNavigationService', () => {
|
|
|
1031
1032
|
})
|
|
1032
1033
|
|
|
1033
1034
|
it('Should not call history.back() on Backspace when focused on a textarea', async () => {
|
|
1034
|
-
await usingAsync(
|
|
1035
|
+
await usingAsync(createInjector(), async (i) => {
|
|
1035
1036
|
configureSpatialNavigation(i, { backspaceGoesBack: true })
|
|
1036
1037
|
|
|
1037
1038
|
const textarea = document.createElement('textarea')
|
|
@@ -1041,7 +1042,7 @@ describe('SpatialNavigationService', () => {
|
|
|
1041
1042
|
textarea.focus()
|
|
1042
1043
|
|
|
1043
1044
|
const backSpy = vi.spyOn(history, 'back').mockImplementation(() => {})
|
|
1044
|
-
i.
|
|
1045
|
+
i.get(SpatialNavigationService)
|
|
1045
1046
|
|
|
1046
1047
|
pressKey('Backspace')
|
|
1047
1048
|
expect(backSpy).not.toHaveBeenCalled()
|
|
@@ -1049,7 +1050,7 @@ describe('SpatialNavigationService', () => {
|
|
|
1049
1050
|
})
|
|
1050
1051
|
|
|
1051
1052
|
it('Should move to parent section on Escape when configured', async () => {
|
|
1052
|
-
await usingAsync(
|
|
1053
|
+
await usingAsync(createInjector(), async (i) => {
|
|
1053
1054
|
configureSpatialNavigation(i, { escapeGoesToParentSection: true })
|
|
1054
1055
|
|
|
1055
1056
|
const outer = document.createElement('div')
|
|
@@ -1065,7 +1066,7 @@ describe('SpatialNavigationService', () => {
|
|
|
1065
1066
|
document.body.append(outer)
|
|
1066
1067
|
|
|
1067
1068
|
innerBtn.focus()
|
|
1068
|
-
i.
|
|
1069
|
+
i.get(SpatialNavigationService)
|
|
1069
1070
|
|
|
1070
1071
|
pressKey('Escape')
|
|
1071
1072
|
expect(document.activeElement).toBe(outerBtn)
|
|
@@ -1073,7 +1074,7 @@ describe('SpatialNavigationService', () => {
|
|
|
1073
1074
|
})
|
|
1074
1075
|
|
|
1075
1076
|
it('Should blur a range input on Escape', async () => {
|
|
1076
|
-
await usingAsync(
|
|
1077
|
+
await usingAsync(createInjector(), async (i) => {
|
|
1077
1078
|
const input = document.createElement('input')
|
|
1078
1079
|
input.type = 'range'
|
|
1079
1080
|
mockRect(input, { left: 0, top: 0, width: 200, height: 30 })
|
|
@@ -1082,14 +1083,14 @@ describe('SpatialNavigationService', () => {
|
|
|
1082
1083
|
input.focus()
|
|
1083
1084
|
expect(document.activeElement).toBe(input)
|
|
1084
1085
|
|
|
1085
|
-
i.
|
|
1086
|
+
i.get(SpatialNavigationService)
|
|
1086
1087
|
pressKey('Escape')
|
|
1087
1088
|
expect(document.activeElement).toBe(document.body)
|
|
1088
1089
|
})
|
|
1089
1090
|
})
|
|
1090
1091
|
|
|
1091
1092
|
it('Should blur a radio input on Escape', async () => {
|
|
1092
|
-
await usingAsync(
|
|
1093
|
+
await usingAsync(createInjector(), async (i) => {
|
|
1093
1094
|
const radio = document.createElement('input')
|
|
1094
1095
|
radio.type = 'radio'
|
|
1095
1096
|
radio.name = 'group'
|
|
@@ -1099,14 +1100,14 @@ describe('SpatialNavigationService', () => {
|
|
|
1099
1100
|
radio.focus()
|
|
1100
1101
|
expect(document.activeElement).toBe(radio)
|
|
1101
1102
|
|
|
1102
|
-
i.
|
|
1103
|
+
i.get(SpatialNavigationService)
|
|
1103
1104
|
pressKey('Escape')
|
|
1104
1105
|
expect(document.activeElement).toBe(document.body)
|
|
1105
1106
|
})
|
|
1106
1107
|
})
|
|
1107
1108
|
|
|
1108
1109
|
it('Should blur a date input on Escape', async () => {
|
|
1109
|
-
await usingAsync(
|
|
1110
|
+
await usingAsync(createInjector(), async (i) => {
|
|
1110
1111
|
const input = document.createElement('input')
|
|
1111
1112
|
input.type = 'date'
|
|
1112
1113
|
mockRect(input, { left: 0, top: 0, width: 200, height: 30 })
|
|
@@ -1115,14 +1116,14 @@ describe('SpatialNavigationService', () => {
|
|
|
1115
1116
|
input.focus()
|
|
1116
1117
|
expect(document.activeElement).toBe(input)
|
|
1117
1118
|
|
|
1118
|
-
i.
|
|
1119
|
+
i.get(SpatialNavigationService)
|
|
1119
1120
|
pressKey('Escape')
|
|
1120
1121
|
expect(document.activeElement).toBe(document.body)
|
|
1121
1122
|
})
|
|
1122
1123
|
})
|
|
1123
1124
|
|
|
1124
1125
|
it('Should blur a time input on Escape', async () => {
|
|
1125
|
-
await usingAsync(
|
|
1126
|
+
await usingAsync(createInjector(), async (i) => {
|
|
1126
1127
|
const input = document.createElement('input')
|
|
1127
1128
|
input.type = 'time'
|
|
1128
1129
|
mockRect(input, { left: 0, top: 0, width: 200, height: 30 })
|
|
@@ -1131,14 +1132,14 @@ describe('SpatialNavigationService', () => {
|
|
|
1131
1132
|
input.focus()
|
|
1132
1133
|
expect(document.activeElement).toBe(input)
|
|
1133
1134
|
|
|
1134
|
-
i.
|
|
1135
|
+
i.get(SpatialNavigationService)
|
|
1135
1136
|
pressKey('Escape')
|
|
1136
1137
|
expect(document.activeElement).toBe(document.body)
|
|
1137
1138
|
})
|
|
1138
1139
|
})
|
|
1139
1140
|
|
|
1140
1141
|
it('Should blur a number input on Escape', async () => {
|
|
1141
|
-
await usingAsync(
|
|
1142
|
+
await usingAsync(createInjector(), async (i) => {
|
|
1142
1143
|
const input = document.createElement('input')
|
|
1143
1144
|
input.type = 'number'
|
|
1144
1145
|
input.value = '5'
|
|
@@ -1148,14 +1149,14 @@ describe('SpatialNavigationService', () => {
|
|
|
1148
1149
|
input.focus()
|
|
1149
1150
|
expect(document.activeElement).toBe(input)
|
|
1150
1151
|
|
|
1151
|
-
i.
|
|
1152
|
+
i.get(SpatialNavigationService)
|
|
1152
1153
|
pressKey('Escape')
|
|
1153
1154
|
expect(document.activeElement).toBe(document.body)
|
|
1154
1155
|
})
|
|
1155
1156
|
})
|
|
1156
1157
|
|
|
1157
1158
|
it('Should blur a text input on Escape', async () => {
|
|
1158
|
-
await usingAsync(
|
|
1159
|
+
await usingAsync(createInjector(), async (i) => {
|
|
1159
1160
|
const input = document.createElement('input')
|
|
1160
1161
|
input.type = 'text'
|
|
1161
1162
|
input.value = 'hello'
|
|
@@ -1165,14 +1166,14 @@ describe('SpatialNavigationService', () => {
|
|
|
1165
1166
|
input.focus()
|
|
1166
1167
|
expect(document.activeElement).toBe(input)
|
|
1167
1168
|
|
|
1168
|
-
i.
|
|
1169
|
+
i.get(SpatialNavigationService)
|
|
1169
1170
|
pressKey('Escape')
|
|
1170
1171
|
expect(document.activeElement).toBe(document.body)
|
|
1171
1172
|
})
|
|
1172
1173
|
})
|
|
1173
1174
|
|
|
1174
1175
|
it('Should blur a textarea on Escape', async () => {
|
|
1175
|
-
await usingAsync(
|
|
1176
|
+
await usingAsync(createInjector(), async (i) => {
|
|
1176
1177
|
const textarea = document.createElement('textarea')
|
|
1177
1178
|
textarea.value = 'hello'
|
|
1178
1179
|
mockRect(textarea, { left: 0, top: 0, width: 200, height: 100 })
|
|
@@ -1181,21 +1182,21 @@ describe('SpatialNavigationService', () => {
|
|
|
1181
1182
|
textarea.focus()
|
|
1182
1183
|
expect(document.activeElement).toBe(textarea)
|
|
1183
1184
|
|
|
1184
|
-
i.
|
|
1185
|
+
i.get(SpatialNavigationService)
|
|
1185
1186
|
pressKey('Escape')
|
|
1186
1187
|
expect(document.activeElement).toBe(document.body)
|
|
1187
1188
|
})
|
|
1188
1189
|
})
|
|
1189
1190
|
|
|
1190
1191
|
it('Should not blur a button on Escape', async () => {
|
|
1191
|
-
await usingAsync(
|
|
1192
|
+
await usingAsync(createInjector(), async (i) => {
|
|
1192
1193
|
const btn = createButton('btn', { left: 0, top: 0, width: 50, height: 50 })
|
|
1193
1194
|
document.body.append(btn)
|
|
1194
1195
|
|
|
1195
1196
|
btn.focus()
|
|
1196
1197
|
expect(document.activeElement).toBe(btn)
|
|
1197
1198
|
|
|
1198
|
-
i.
|
|
1199
|
+
i.get(SpatialNavigationService)
|
|
1199
1200
|
pressKey('Escape')
|
|
1200
1201
|
expect(document.activeElement).toBe(btn)
|
|
1201
1202
|
})
|
|
@@ -1204,7 +1205,7 @@ describe('SpatialNavigationService', () => {
|
|
|
1204
1205
|
|
|
1205
1206
|
describe('data-spatial-nav-target', () => {
|
|
1206
1207
|
it('Should treat elements with data-spatial-nav-target as focusable candidates', async () => {
|
|
1207
|
-
await usingAsync(
|
|
1208
|
+
await usingAsync(createInjector(), async (i) => {
|
|
1208
1209
|
const origin = createButton('origin', { left: 0, top: 0, width: 50, height: 50 })
|
|
1209
1210
|
const target = document.createElement('div')
|
|
1210
1211
|
target.setAttribute('data-spatial-nav-target', '')
|
|
@@ -1216,7 +1217,7 @@ describe('SpatialNavigationService', () => {
|
|
|
1216
1217
|
document.body.append(origin, target)
|
|
1217
1218
|
|
|
1218
1219
|
origin.focus()
|
|
1219
|
-
const s = i.
|
|
1220
|
+
const s = i.get(SpatialNavigationService)
|
|
1220
1221
|
s.moveFocus('right')
|
|
1221
1222
|
expect(document.activeElement).toBe(target)
|
|
1222
1223
|
})
|
|
@@ -1225,7 +1226,7 @@ describe('SpatialNavigationService', () => {
|
|
|
1225
1226
|
|
|
1226
1227
|
describe('overflow-aware visibility', () => {
|
|
1227
1228
|
it('Should skip candidates whose center is outside their overflow container', async () => {
|
|
1228
|
-
await usingAsync(
|
|
1229
|
+
await usingAsync(createInjector(), async (i) => {
|
|
1229
1230
|
const container = document.createElement('div')
|
|
1230
1231
|
Object.defineProperty(container, 'computedStyleMap', { value: () => new Map() })
|
|
1231
1232
|
vi.spyOn(window, 'getComputedStyle').mockImplementation((el) => {
|
|
@@ -1244,7 +1245,7 @@ describe('SpatialNavigationService', () => {
|
|
|
1244
1245
|
document.body.append(container, origin)
|
|
1245
1246
|
|
|
1246
1247
|
origin.focus()
|
|
1247
|
-
const s = i.
|
|
1248
|
+
const s = i.get(SpatialNavigationService)
|
|
1248
1249
|
s.moveFocus('up')
|
|
1249
1250
|
expect(document.activeElement).toBe(visible)
|
|
1250
1251
|
})
|
|
@@ -1253,7 +1254,7 @@ describe('SpatialNavigationService', () => {
|
|
|
1253
1254
|
|
|
1254
1255
|
describe('focus trap', () => {
|
|
1255
1256
|
it('Should block cross-section navigation when a trap is active', async () => {
|
|
1256
|
-
await usingAsync(
|
|
1257
|
+
await usingAsync(createInjector(), async (i) => {
|
|
1257
1258
|
const section1 = document.createElement('div')
|
|
1258
1259
|
section1.setAttribute('data-nav-section', 'modal')
|
|
1259
1260
|
mockRect(section1, { left: 0, top: 0, width: 400, height: 400 })
|
|
@@ -1270,7 +1271,7 @@ describe('SpatialNavigationService', () => {
|
|
|
1270
1271
|
|
|
1271
1272
|
btn1.focus()
|
|
1272
1273
|
|
|
1273
|
-
const s = i.
|
|
1274
|
+
const s = i.get(SpatialNavigationService)
|
|
1274
1275
|
s.pushFocusTrap('modal')
|
|
1275
1276
|
|
|
1276
1277
|
s.moveFocus('right')
|
|
@@ -1279,16 +1280,16 @@ describe('SpatialNavigationService', () => {
|
|
|
1279
1280
|
})
|
|
1280
1281
|
|
|
1281
1282
|
it('Should set activeSection when pushing a trap', async () => {
|
|
1282
|
-
await usingAsync(
|
|
1283
|
-
const s = i.
|
|
1283
|
+
await usingAsync(createInjector(), async (i) => {
|
|
1284
|
+
const s = i.get(SpatialNavigationService)
|
|
1284
1285
|
s.pushFocusTrap('dialog')
|
|
1285
1286
|
expect(s.activeSection.getValue()).toBe('dialog')
|
|
1286
1287
|
})
|
|
1287
1288
|
})
|
|
1288
1289
|
|
|
1289
1290
|
it('Should restore previous section on pop', async () => {
|
|
1290
|
-
await usingAsync(
|
|
1291
|
-
const s = i.
|
|
1291
|
+
await usingAsync(createInjector(), async (i) => {
|
|
1292
|
+
const s = i.get(SpatialNavigationService)
|
|
1292
1293
|
s.activeSection.setValue('main')
|
|
1293
1294
|
s.pushFocusTrap('dialog')
|
|
1294
1295
|
expect(s.activeSection.getValue()).toBe('dialog')
|
|
@@ -1299,8 +1300,8 @@ describe('SpatialNavigationService', () => {
|
|
|
1299
1300
|
})
|
|
1300
1301
|
|
|
1301
1302
|
it('Should support nested traps — topmost wins', async () => {
|
|
1302
|
-
await usingAsync(
|
|
1303
|
-
const s = i.
|
|
1303
|
+
await usingAsync(createInjector(), async (i) => {
|
|
1304
|
+
const s = i.get(SpatialNavigationService)
|
|
1304
1305
|
s.pushFocusTrap('outer-dialog')
|
|
1305
1306
|
s.pushFocusTrap('inner-dialog')
|
|
1306
1307
|
expect(s.activeSection.getValue()).toBe('inner-dialog')
|
|
@@ -1314,7 +1315,7 @@ describe('SpatialNavigationService', () => {
|
|
|
1314
1315
|
})
|
|
1315
1316
|
|
|
1316
1317
|
it('Should allow within-section navigation when trap is active', async () => {
|
|
1317
|
-
await usingAsync(
|
|
1318
|
+
await usingAsync(createInjector(), async (i) => {
|
|
1318
1319
|
const section = document.createElement('div')
|
|
1319
1320
|
section.setAttribute('data-nav-section', 'modal')
|
|
1320
1321
|
mockRect(section, { left: 0, top: 0, width: 400, height: 400 })
|
|
@@ -1325,7 +1326,7 @@ describe('SpatialNavigationService', () => {
|
|
|
1325
1326
|
|
|
1326
1327
|
btn1.focus()
|
|
1327
1328
|
|
|
1328
|
-
const s = i.
|
|
1329
|
+
const s = i.get(SpatialNavigationService)
|
|
1329
1330
|
s.pushFocusTrap('modal')
|
|
1330
1331
|
|
|
1331
1332
|
s.moveFocus('down')
|
|
@@ -1334,7 +1335,7 @@ describe('SpatialNavigationService', () => {
|
|
|
1334
1335
|
})
|
|
1335
1336
|
|
|
1336
1337
|
it('Should focus within trapped section when activeElement is document.body', async () => {
|
|
1337
|
-
await usingAsync(
|
|
1338
|
+
await usingAsync(createInjector(), async (i) => {
|
|
1338
1339
|
const section1 = document.createElement('div')
|
|
1339
1340
|
section1.setAttribute('data-nav-section', 'sidebar')
|
|
1340
1341
|
mockRect(section1, { left: 0, top: 0, width: 200, height: 400 })
|
|
@@ -1353,7 +1354,7 @@ describe('SpatialNavigationService', () => {
|
|
|
1353
1354
|
;(document.activeElement as HTMLElement)?.blur()
|
|
1354
1355
|
expect(document.activeElement).toBe(document.body)
|
|
1355
1356
|
|
|
1356
|
-
const s = i.
|
|
1357
|
+
const s = i.get(SpatialNavigationService)
|
|
1357
1358
|
s.pushFocusTrap('modal')
|
|
1358
1359
|
|
|
1359
1360
|
s.moveFocus('down')
|
|
@@ -1363,13 +1364,13 @@ describe('SpatialNavigationService', () => {
|
|
|
1363
1364
|
})
|
|
1364
1365
|
|
|
1365
1366
|
it('Should clear trap stack on disposal', async () => {
|
|
1366
|
-
await usingAsync(
|
|
1367
|
-
const s = i.
|
|
1367
|
+
await usingAsync(createInjector(), async (i) => {
|
|
1368
|
+
const s = i.get(SpatialNavigationService)
|
|
1368
1369
|
s.pushFocusTrap('dialog')
|
|
1369
1370
|
})
|
|
1370
1371
|
// After disposal, creating a new instance should have no traps
|
|
1371
|
-
await usingAsync(
|
|
1372
|
-
const s = i.
|
|
1372
|
+
await usingAsync(createInjector(), async (i) => {
|
|
1373
|
+
const s = i.get(SpatialNavigationService)
|
|
1373
1374
|
expect(s.activeSection.getValue()).toBeNull()
|
|
1374
1375
|
})
|
|
1375
1376
|
})
|
|
@@ -1377,19 +1378,23 @@ describe('SpatialNavigationService', () => {
|
|
|
1377
1378
|
|
|
1378
1379
|
describe('configureSpatialNavigation', () => {
|
|
1379
1380
|
it('Should configure the service with custom options', async () => {
|
|
1380
|
-
await usingAsync(
|
|
1381
|
+
await usingAsync(createInjector(), async (i) => {
|
|
1381
1382
|
configureSpatialNavigation(i, { initiallyEnabled: false })
|
|
1382
|
-
const s = i.
|
|
1383
|
+
const s = i.get(SpatialNavigationService)
|
|
1383
1384
|
expect(s.enabled.getValue()).toBe(false)
|
|
1384
1385
|
})
|
|
1385
1386
|
})
|
|
1386
1387
|
|
|
1387
|
-
it('Should
|
|
1388
|
-
await usingAsync(
|
|
1389
|
-
i.
|
|
1390
|
-
expect(()
|
|
1391
|
-
|
|
1392
|
-
)
|
|
1388
|
+
it('Should rebind options when called after the service is instantiated', async () => {
|
|
1389
|
+
await usingAsync(createInjector(), async (i) => {
|
|
1390
|
+
const first = i.get(SpatialNavigationService)
|
|
1391
|
+
expect(first.enabled.getValue()).toBe(true)
|
|
1392
|
+
|
|
1393
|
+
configureSpatialNavigation(i, { initiallyEnabled: false })
|
|
1394
|
+
|
|
1395
|
+
const second = i.get(SpatialNavigationService)
|
|
1396
|
+
expect(second).not.toBe(first)
|
|
1397
|
+
expect(second.enabled.getValue()).toBe(false)
|
|
1393
1398
|
})
|
|
1394
1399
|
})
|
|
1395
1400
|
})
|