@navios/di-react 0.8.0 → 1.0.0-alpha.3
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 +63 -0
- package/README.md +14 -67
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/clover.xml +270 -0
- package/coverage/coverage-final.json +10 -0
- package/coverage/favicon.png +0 -0
- package/coverage/hooks/index.html +191 -0
- package/coverage/hooks/use-container.mts.html +268 -0
- package/coverage/hooks/use-invalidate.mts.html +208 -0
- package/coverage/hooks/use-optional-service.mts.html +910 -0
- package/coverage/hooks/use-scope.mts.html +346 -0
- package/coverage/hooks/use-service.mts.html +760 -0
- package/coverage/hooks/use-suspense-service.mts.html +784 -0
- package/coverage/index.html +131 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/providers/container-provider.mts.html +139 -0
- package/coverage/providers/context.mts.html +130 -0
- package/coverage/providers/index.html +146 -0
- package/coverage/providers/scope-provider.mts.html +355 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/lib/index.d.mts +3 -57
- package/lib/index.d.mts.map +1 -1
- package/lib/index.d.ts +3 -57
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +59 -72
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +60 -72
- package/lib/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/hooks/__tests__/use-container.spec.mts +95 -4
- package/src/hooks/__tests__/use-invalidate.spec.mts +10 -146
- package/src/hooks/__tests__/use-scope.spec.mts +293 -0
- package/src/hooks/__tests__/use-service.spec.mts +2 -2
- package/src/hooks/index.mts +1 -1
- package/src/hooks/use-container.mts +6 -3
- package/src/hooks/use-invalidate.mts +1 -82
- package/src/hooks/use-optional-service.mts +23 -32
- package/src/hooks/use-service.mts +31 -33
- package/src/hooks/use-suspense-service.mts +16 -24
- package/src/providers/__tests__/scope-provider.spec.mts +5 -2
- package/src/providers/scope-provider.mts +1 -8
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,69 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.0.0-alpha.3] - 2026-01-08
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Comprehensive Test Suite**: Added unit tests for hooks
|
|
13
|
+
- `use-scope.spec.mts` - Scope hook tests
|
|
14
|
+
- Extended `use-container.spec.mts` with additional test cases
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **Improved Warning Message**: Changed `useService` args instability warning from `console.log` to `console.warn`
|
|
19
|
+
- Better semantic meaning for warning messages
|
|
20
|
+
- Improved message formatting and readability
|
|
21
|
+
|
|
22
|
+
### Dependencies
|
|
23
|
+
|
|
24
|
+
- Updated to support `@navios/di` ^1.0.0-alpha.3
|
|
25
|
+
|
|
26
|
+
## [0.9.0] - 2025-12-23
|
|
27
|
+
|
|
28
|
+
### Breaking Changes
|
|
29
|
+
|
|
30
|
+
- **BREAKING**: Removed `useInvalidate` hook
|
|
31
|
+
- This hook relied on the `ServiceLocator` API which was removed in `@navios/di` 0.9.0
|
|
32
|
+
- **Migration**: Use `useInvalidateInstance` instead, which invalidates by instance reference
|
|
33
|
+
- Alternative: Use `container.invalidate(instance)` directly via `useContainer()`
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
// Before (0.8.x)
|
|
37
|
+
const invalidateUser = useInvalidate(UserService)
|
|
38
|
+
invalidateUser()
|
|
39
|
+
|
|
40
|
+
// After (0.9.0) - Option 1: useInvalidateInstance
|
|
41
|
+
const { data: user } = useService(UserService)
|
|
42
|
+
const invalidateInstance = useInvalidateInstance()
|
|
43
|
+
invalidateInstance(user)
|
|
44
|
+
|
|
45
|
+
// After (0.9.0) - Option 2: Direct container access
|
|
46
|
+
const container = useContainer()
|
|
47
|
+
const { data: user } = useService(UserService)
|
|
48
|
+
await container.invalidate(user)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
|
|
53
|
+
- **Simplified Instance Name Generation**: Refactored all hooks to use the new `container.calculateInstanceName()` method
|
|
54
|
+
- Removed direct access to internal DI components (`getTokenResolver()`, `getNameResolver()`)
|
|
55
|
+
- Cleaner, more maintainable code with less coupling to DI internals
|
|
56
|
+
- **Improved Event Subscription Setup**: Fixed race conditions in invalidation subscription
|
|
57
|
+
- `useOptionalService`: Removed `setTimeout` hack, now properly awaits service fetch before subscribing
|
|
58
|
+
- `useService`: Simplified subscription logic with proper null checks
|
|
59
|
+
- `useSuspenseService`: Streamlined instance name calculation
|
|
60
|
+
|
|
61
|
+
### Fixed
|
|
62
|
+
|
|
63
|
+
- **Instance Name Generation**: Fixed edge cases where instance names could be incorrectly calculated
|
|
64
|
+
- Now properly handles cases where `calculateInstanceName` returns null
|
|
65
|
+
- Prevents potential subscription errors with undefined instance names
|
|
66
|
+
|
|
67
|
+
### Dependencies
|
|
68
|
+
|
|
69
|
+
- Updated to support `@navios/di` ^0.9.0 with new unified storage architecture
|
|
70
|
+
|
|
8
71
|
## [0.8.0] - 2025-12-21
|
|
9
72
|
|
|
10
73
|
### Dependencies
|
package/README.md
CHANGED
|
@@ -282,50 +282,6 @@ function Analytics() {
|
|
|
282
282
|
}
|
|
283
283
|
```
|
|
284
284
|
|
|
285
|
-
### useInvalidate
|
|
286
|
-
|
|
287
|
-
Get a function to invalidate a service by its token. When called, this will destroy the current service instance and trigger re-fetch in all components using `useService`/`useSuspenseService` for that token.
|
|
288
|
-
|
|
289
|
-
```tsx
|
|
290
|
-
import { useService, useInvalidate } from '@navios/di-react'
|
|
291
|
-
|
|
292
|
-
function UserProfile() {
|
|
293
|
-
const { data: user } = useService(UserService)
|
|
294
|
-
const invalidateUser = useInvalidate(UserService)
|
|
295
|
-
|
|
296
|
-
const handleRefresh = () => {
|
|
297
|
-
invalidateUser() // All components using UserService will re-fetch
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
return (
|
|
301
|
-
<div>
|
|
302
|
-
<span>{user?.name}</span>
|
|
303
|
-
<button onClick={handleRefresh}>Refresh</button>
|
|
304
|
-
</div>
|
|
305
|
-
)
|
|
306
|
-
}
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
#### With Arguments
|
|
310
|
-
|
|
311
|
-
```tsx
|
|
312
|
-
import { useMemo } from 'react'
|
|
313
|
-
import { useService, useInvalidate } from '@navios/di-react'
|
|
314
|
-
|
|
315
|
-
function UserProfile({ userId }: { userId: string }) {
|
|
316
|
-
const args = useMemo(() => ({ userId }), [userId])
|
|
317
|
-
const { data: user } = useService(UserToken, args)
|
|
318
|
-
const invalidateUser = useInvalidate(UserToken, args)
|
|
319
|
-
|
|
320
|
-
return (
|
|
321
|
-
<div>
|
|
322
|
-
<span>{user?.name}</span>
|
|
323
|
-
<button onClick={() => invalidateUser()}>Refresh</button>
|
|
324
|
-
</div>
|
|
325
|
-
)
|
|
326
|
-
}
|
|
327
|
-
```
|
|
328
|
-
|
|
329
285
|
### useInvalidateInstance
|
|
330
286
|
|
|
331
287
|
Invalidate a service instance directly without knowing its token.
|
|
@@ -441,7 +397,7 @@ function ChildComponent() {
|
|
|
441
397
|
|
|
442
398
|
## Service Invalidation
|
|
443
399
|
|
|
444
|
-
Both `useService` and `useSuspenseService` automatically subscribe to service invalidation events via the DI container's event bus. When a service is invalidated (e.g., via `container.invalidate(service)` or `
|
|
400
|
+
Both `useService` and `useSuspenseService` automatically subscribe to service invalidation events via the DI container's event bus. When a service is invalidated (e.g., via `container.invalidate(service)` or `useInvalidateInstance`), the hooks will automatically:
|
|
445
401
|
|
|
446
402
|
1. Clear the cached instance
|
|
447
403
|
2. Re-fetch the service
|
|
@@ -456,11 +412,13 @@ This enables reactive updates when services change, making it easy to implement
|
|
|
456
412
|
```tsx
|
|
457
413
|
function UserList() {
|
|
458
414
|
const { data: users } = useService(UserService)
|
|
459
|
-
const
|
|
415
|
+
const invalidateInstance = useInvalidateInstance()
|
|
460
416
|
|
|
461
417
|
const handleCreateUser = async () => {
|
|
462
418
|
await createUser(newUser)
|
|
463
|
-
|
|
419
|
+
if (users) {
|
|
420
|
+
invalidateInstance(users) // Automatically refreshes all components using UserService
|
|
421
|
+
}
|
|
464
422
|
}
|
|
465
423
|
|
|
466
424
|
return (
|
|
@@ -640,18 +598,6 @@ interface UseOptionalServiceResult<T> {
|
|
|
640
598
|
|
|
641
599
|
Loads a service that may not be registered. Returns `isNotFound: true` when the service doesn't exist instead of throwing an error.
|
|
642
600
|
|
|
643
|
-
### useInvalidate
|
|
644
|
-
|
|
645
|
-
```ts
|
|
646
|
-
function useInvalidate<T>(token: ClassType): () => Promise<void>
|
|
647
|
-
function useInvalidate<T, S>(
|
|
648
|
-
token: InjectionToken<T, S>,
|
|
649
|
-
args: S extends undefined ? never : unknown,
|
|
650
|
-
): () => Promise<void>
|
|
651
|
-
```
|
|
652
|
-
|
|
653
|
-
Returns a function to invalidate a service by its token. When called, destroys the current service instance and triggers re-fetch in all components using `useService`/`useSuspenseService` for that token.
|
|
654
|
-
|
|
655
601
|
### useInvalidateInstance
|
|
656
602
|
|
|
657
603
|
```ts
|
|
@@ -739,17 +685,18 @@ Make sure you've wrapped your component with both `Suspense` and an error bounda
|
|
|
739
685
|
|
|
740
686
|
### Services not invalidating
|
|
741
687
|
|
|
742
|
-
|
|
688
|
+
When using `useInvalidateInstance`, make sure you have a reference to the service instance:
|
|
743
689
|
|
|
744
690
|
```tsx
|
|
745
|
-
// ✅ Good -
|
|
746
|
-
const
|
|
747
|
-
const
|
|
748
|
-
const invalidate = useInvalidate(UserToken, args)
|
|
691
|
+
// ✅ Good - invalidate the actual instance
|
|
692
|
+
const { data: user } = useService(UserService)
|
|
693
|
+
const invalidateInstance = useInvalidateInstance()
|
|
749
694
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
695
|
+
const handleRefresh = () => {
|
|
696
|
+
if (user) {
|
|
697
|
+
invalidateInstance(user)
|
|
698
|
+
}
|
|
699
|
+
}
|
|
753
700
|
```
|
|
754
701
|
|
|
755
702
|
## License
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
body, html {
|
|
2
|
+
margin:0; padding: 0;
|
|
3
|
+
height: 100%;
|
|
4
|
+
}
|
|
5
|
+
body {
|
|
6
|
+
font-family: Helvetica Neue, Helvetica, Arial;
|
|
7
|
+
font-size: 14px;
|
|
8
|
+
color:#333;
|
|
9
|
+
}
|
|
10
|
+
.small { font-size: 12px; }
|
|
11
|
+
*, *:after, *:before {
|
|
12
|
+
-webkit-box-sizing:border-box;
|
|
13
|
+
-moz-box-sizing:border-box;
|
|
14
|
+
box-sizing:border-box;
|
|
15
|
+
}
|
|
16
|
+
h1 { font-size: 20px; margin: 0;}
|
|
17
|
+
h2 { font-size: 14px; }
|
|
18
|
+
pre {
|
|
19
|
+
font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
|
20
|
+
margin: 0;
|
|
21
|
+
padding: 0;
|
|
22
|
+
-moz-tab-size: 2;
|
|
23
|
+
-o-tab-size: 2;
|
|
24
|
+
tab-size: 2;
|
|
25
|
+
}
|
|
26
|
+
a { color:#0074D9; text-decoration:none; }
|
|
27
|
+
a:hover { text-decoration:underline; }
|
|
28
|
+
.strong { font-weight: bold; }
|
|
29
|
+
.space-top1 { padding: 10px 0 0 0; }
|
|
30
|
+
.pad2y { padding: 20px 0; }
|
|
31
|
+
.pad1y { padding: 10px 0; }
|
|
32
|
+
.pad2x { padding: 0 20px; }
|
|
33
|
+
.pad2 { padding: 20px; }
|
|
34
|
+
.pad1 { padding: 10px; }
|
|
35
|
+
.space-left2 { padding-left:55px; }
|
|
36
|
+
.space-right2 { padding-right:20px; }
|
|
37
|
+
.center { text-align:center; }
|
|
38
|
+
.clearfix { display:block; }
|
|
39
|
+
.clearfix:after {
|
|
40
|
+
content:'';
|
|
41
|
+
display:block;
|
|
42
|
+
height:0;
|
|
43
|
+
clear:both;
|
|
44
|
+
visibility:hidden;
|
|
45
|
+
}
|
|
46
|
+
.fl { float: left; }
|
|
47
|
+
@media only screen and (max-width:640px) {
|
|
48
|
+
.col3 { width:100%; max-width:100%; }
|
|
49
|
+
.hide-mobile { display:none!important; }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.quiet {
|
|
53
|
+
color: #7f7f7f;
|
|
54
|
+
color: rgba(0,0,0,0.5);
|
|
55
|
+
}
|
|
56
|
+
.quiet a { opacity: 0.7; }
|
|
57
|
+
|
|
58
|
+
.fraction {
|
|
59
|
+
font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
|
|
60
|
+
font-size: 10px;
|
|
61
|
+
color: #555;
|
|
62
|
+
background: #E8E8E8;
|
|
63
|
+
padding: 4px 5px;
|
|
64
|
+
border-radius: 3px;
|
|
65
|
+
vertical-align: middle;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
div.path a:link, div.path a:visited { color: #333; }
|
|
69
|
+
table.coverage {
|
|
70
|
+
border-collapse: collapse;
|
|
71
|
+
margin: 10px 0 0 0;
|
|
72
|
+
padding: 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
table.coverage td {
|
|
76
|
+
margin: 0;
|
|
77
|
+
padding: 0;
|
|
78
|
+
vertical-align: top;
|
|
79
|
+
}
|
|
80
|
+
table.coverage td.line-count {
|
|
81
|
+
text-align: right;
|
|
82
|
+
padding: 0 5px 0 20px;
|
|
83
|
+
}
|
|
84
|
+
table.coverage td.line-coverage {
|
|
85
|
+
text-align: right;
|
|
86
|
+
padding-right: 10px;
|
|
87
|
+
min-width:20px;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
table.coverage td span.cline-any {
|
|
91
|
+
display: inline-block;
|
|
92
|
+
padding: 0 5px;
|
|
93
|
+
width: 100%;
|
|
94
|
+
}
|
|
95
|
+
.missing-if-branch {
|
|
96
|
+
display: inline-block;
|
|
97
|
+
margin-right: 5px;
|
|
98
|
+
border-radius: 3px;
|
|
99
|
+
position: relative;
|
|
100
|
+
padding: 0 4px;
|
|
101
|
+
background: #333;
|
|
102
|
+
color: yellow;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.skip-if-branch {
|
|
106
|
+
display: none;
|
|
107
|
+
margin-right: 10px;
|
|
108
|
+
position: relative;
|
|
109
|
+
padding: 0 4px;
|
|
110
|
+
background: #ccc;
|
|
111
|
+
color: white;
|
|
112
|
+
}
|
|
113
|
+
.missing-if-branch .typ, .skip-if-branch .typ {
|
|
114
|
+
color: inherit !important;
|
|
115
|
+
}
|
|
116
|
+
.coverage-summary {
|
|
117
|
+
border-collapse: collapse;
|
|
118
|
+
width: 100%;
|
|
119
|
+
}
|
|
120
|
+
.coverage-summary tr { border-bottom: 1px solid #bbb; }
|
|
121
|
+
.keyline-all { border: 1px solid #ddd; }
|
|
122
|
+
.coverage-summary td, .coverage-summary th { padding: 10px; }
|
|
123
|
+
.coverage-summary tbody { border: 1px solid #bbb; }
|
|
124
|
+
.coverage-summary td { border-right: 1px solid #bbb; }
|
|
125
|
+
.coverage-summary td:last-child { border-right: none; }
|
|
126
|
+
.coverage-summary th {
|
|
127
|
+
text-align: left;
|
|
128
|
+
font-weight: normal;
|
|
129
|
+
white-space: nowrap;
|
|
130
|
+
}
|
|
131
|
+
.coverage-summary th.file { border-right: none !important; }
|
|
132
|
+
.coverage-summary th.pct { }
|
|
133
|
+
.coverage-summary th.pic,
|
|
134
|
+
.coverage-summary th.abs,
|
|
135
|
+
.coverage-summary td.pct,
|
|
136
|
+
.coverage-summary td.abs { text-align: right; }
|
|
137
|
+
.coverage-summary td.file { white-space: nowrap; }
|
|
138
|
+
.coverage-summary td.pic { min-width: 120px !important; }
|
|
139
|
+
.coverage-summary tfoot td { }
|
|
140
|
+
|
|
141
|
+
.coverage-summary .sorter {
|
|
142
|
+
height: 10px;
|
|
143
|
+
width: 7px;
|
|
144
|
+
display: inline-block;
|
|
145
|
+
margin-left: 0.5em;
|
|
146
|
+
background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
|
|
147
|
+
}
|
|
148
|
+
.coverage-summary .sorted .sorter {
|
|
149
|
+
background-position: 0 -20px;
|
|
150
|
+
}
|
|
151
|
+
.coverage-summary .sorted-desc .sorter {
|
|
152
|
+
background-position: 0 -10px;
|
|
153
|
+
}
|
|
154
|
+
.status-line { height: 10px; }
|
|
155
|
+
/* yellow */
|
|
156
|
+
.cbranch-no { background: yellow !important; color: #111; }
|
|
157
|
+
/* dark red */
|
|
158
|
+
.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
|
|
159
|
+
.low .chart { border:1px solid #C21F39 }
|
|
160
|
+
.highlighted,
|
|
161
|
+
.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{
|
|
162
|
+
background: #C21F39 !important;
|
|
163
|
+
}
|
|
164
|
+
/* medium red */
|
|
165
|
+
.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
|
|
166
|
+
/* light red */
|
|
167
|
+
.low, .cline-no { background:#FCE1E5 }
|
|
168
|
+
/* light green */
|
|
169
|
+
.high, .cline-yes { background:rgb(230,245,208) }
|
|
170
|
+
/* medium green */
|
|
171
|
+
.cstat-yes { background:rgb(161,215,106) }
|
|
172
|
+
/* dark green */
|
|
173
|
+
.status-line.high, .high .cover-fill { background:rgb(77,146,33) }
|
|
174
|
+
.high .chart { border:1px solid rgb(77,146,33) }
|
|
175
|
+
/* dark yellow (gold) */
|
|
176
|
+
.status-line.medium, .medium .cover-fill { background: #f9cd0b; }
|
|
177
|
+
.medium .chart { border:1px solid #f9cd0b; }
|
|
178
|
+
/* light yellow */
|
|
179
|
+
.medium { background: #fff4c2; }
|
|
180
|
+
|
|
181
|
+
.cstat-skip { background: #ddd; color: #111; }
|
|
182
|
+
.fstat-skip { background: #ddd; color: #111 !important; }
|
|
183
|
+
.cbranch-skip { background: #ddd !important; color: #111; }
|
|
184
|
+
|
|
185
|
+
span.cline-neutral { background: #eaeaea; }
|
|
186
|
+
|
|
187
|
+
.coverage-summary td.empty {
|
|
188
|
+
opacity: .5;
|
|
189
|
+
padding-top: 4px;
|
|
190
|
+
padding-bottom: 4px;
|
|
191
|
+
line-height: 1;
|
|
192
|
+
color: #888;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.cover-fill, .cover-empty {
|
|
196
|
+
display:inline-block;
|
|
197
|
+
height: 12px;
|
|
198
|
+
}
|
|
199
|
+
.chart {
|
|
200
|
+
line-height: 0;
|
|
201
|
+
}
|
|
202
|
+
.cover-empty {
|
|
203
|
+
background: white;
|
|
204
|
+
}
|
|
205
|
+
.cover-full {
|
|
206
|
+
border-right: none !important;
|
|
207
|
+
}
|
|
208
|
+
pre.prettyprint {
|
|
209
|
+
border: none !important;
|
|
210
|
+
padding: 0 !important;
|
|
211
|
+
margin: 0 !important;
|
|
212
|
+
}
|
|
213
|
+
.com { color: #999 !important; }
|
|
214
|
+
.ignore-none { color: #999; font-weight: normal; }
|
|
215
|
+
|
|
216
|
+
.wrapper {
|
|
217
|
+
min-height: 100%;
|
|
218
|
+
height: auto !important;
|
|
219
|
+
height: 100%;
|
|
220
|
+
margin: 0 auto -48px;
|
|
221
|
+
}
|
|
222
|
+
.footer, .push {
|
|
223
|
+
height: 48px;
|
|
224
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
var jumpToCode = (function init() {
|
|
3
|
+
// Classes of code we would like to highlight in the file view
|
|
4
|
+
var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no'];
|
|
5
|
+
|
|
6
|
+
// Elements to highlight in the file listing view
|
|
7
|
+
var fileListingElements = ['td.pct.low'];
|
|
8
|
+
|
|
9
|
+
// We don't want to select elements that are direct descendants of another match
|
|
10
|
+
var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > `
|
|
11
|
+
|
|
12
|
+
// Selector that finds elements on the page to which we can jump
|
|
13
|
+
var selector =
|
|
14
|
+
fileListingElements.join(', ') +
|
|
15
|
+
', ' +
|
|
16
|
+
notSelector +
|
|
17
|
+
missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b`
|
|
18
|
+
|
|
19
|
+
// The NodeList of matching elements
|
|
20
|
+
var missingCoverageElements = document.querySelectorAll(selector);
|
|
21
|
+
|
|
22
|
+
var currentIndex;
|
|
23
|
+
|
|
24
|
+
function toggleClass(index) {
|
|
25
|
+
missingCoverageElements
|
|
26
|
+
.item(currentIndex)
|
|
27
|
+
.classList.remove('highlighted');
|
|
28
|
+
missingCoverageElements.item(index).classList.add('highlighted');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function makeCurrent(index) {
|
|
32
|
+
toggleClass(index);
|
|
33
|
+
currentIndex = index;
|
|
34
|
+
missingCoverageElements.item(index).scrollIntoView({
|
|
35
|
+
behavior: 'smooth',
|
|
36
|
+
block: 'center',
|
|
37
|
+
inline: 'center'
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function goToPrevious() {
|
|
42
|
+
var nextIndex = 0;
|
|
43
|
+
if (typeof currentIndex !== 'number' || currentIndex === 0) {
|
|
44
|
+
nextIndex = missingCoverageElements.length - 1;
|
|
45
|
+
} else if (missingCoverageElements.length > 1) {
|
|
46
|
+
nextIndex = currentIndex - 1;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
makeCurrent(nextIndex);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function goToNext() {
|
|
53
|
+
var nextIndex = 0;
|
|
54
|
+
|
|
55
|
+
if (
|
|
56
|
+
typeof currentIndex === 'number' &&
|
|
57
|
+
currentIndex < missingCoverageElements.length - 1
|
|
58
|
+
) {
|
|
59
|
+
nextIndex = currentIndex + 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
makeCurrent(nextIndex);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return function jump(event) {
|
|
66
|
+
if (
|
|
67
|
+
document.getElementById('fileSearch') === document.activeElement &&
|
|
68
|
+
document.activeElement != null
|
|
69
|
+
) {
|
|
70
|
+
// if we're currently focused on the search input, we don't want to navigate
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
switch (event.which) {
|
|
75
|
+
case 78: // n
|
|
76
|
+
case 74: // j
|
|
77
|
+
goToNext();
|
|
78
|
+
break;
|
|
79
|
+
case 66: // b
|
|
80
|
+
case 75: // k
|
|
81
|
+
case 80: // p
|
|
82
|
+
goToPrevious();
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
})();
|
|
87
|
+
window.addEventListener('keydown', jumpToCode);
|