Skip to content

Commit f1cb1a6

Browse files
committed
refactor(core): unify focusManager and onlineManager
into an eventManager, which is function based
1 parent 96b7895 commit f1cb1a6

File tree

5 files changed

+140
-152
lines changed

5 files changed

+140
-152
lines changed

src/core/eventManager.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { isServer } from './utils'
2+
3+
type ListenerFn = () => void
4+
5+
export function createEventManager(
6+
events: ReadonlyArray<Parameters<typeof window['addEventListener']>[0]>
7+
) {
8+
let value: boolean | undefined
9+
let removeEventListener: ListenerFn | undefined
10+
let listeners: ListenerFn[] = []
11+
let setupFn: (
12+
param: (newValue?: boolean) => void
13+
) => ListenerFn | undefined = onEvent => {
14+
if (!isServer && window?.addEventListener) {
15+
const listener = () => onEvent()
16+
events.forEach(eventName => {
17+
window.addEventListener(eventName, listener, false)
18+
})
19+
20+
return () => {
21+
events.forEach(eventName => {
22+
window.removeEventListener(eventName, listener)
23+
})
24+
}
25+
}
26+
}
27+
28+
const subscribe = (listener: ListenerFn): ListenerFn => {
29+
listeners.push(listener)
30+
31+
if (!removeEventListener) {
32+
setEventListener(setupFn)
33+
}
34+
35+
return () => {
36+
listeners = listeners.filter(x => x !== listener)
37+
if (listeners.length === 0) {
38+
removeEventListener?.()
39+
removeEventListener = undefined
40+
}
41+
}
42+
}
43+
44+
const setValue = (newValue?: boolean): void => {
45+
value = newValue
46+
47+
if (newValue) {
48+
onEvent()
49+
}
50+
}
51+
52+
const setEventListener = (
53+
setup: (param: (newValue?: boolean) => void) => ListenerFn | undefined
54+
): void => {
55+
removeEventListener?.()
56+
setupFn = setup
57+
removeEventListener = setupFn(newValue => {
58+
if (typeof newValue === 'boolean') {
59+
setValue(newValue)
60+
} else {
61+
onEvent()
62+
}
63+
})
64+
}
65+
66+
const onEvent = (): void => {
67+
listeners.forEach(listener => {
68+
listener()
69+
})
70+
}
71+
72+
return {
73+
setEventListener,
74+
subscribe,
75+
setValue,
76+
getValue: () => value,
77+
} as const
78+
}

src/core/focusManager.ts

Lines changed: 25 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,31 @@
1-
import { Subscribable } from './subscribable'
2-
import { isServer } from './utils'
3-
4-
class FocusManager extends Subscribable {
5-
private focused?: boolean
6-
private removeEventListener?: () => void
7-
8-
protected onSubscribe(): void {
9-
if (!this.removeEventListener) {
10-
this.setDefaultEventListener()
11-
}
12-
}
13-
14-
setEventListener(
15-
setup: (setFocused: (focused?: boolean) => void) => () => void
16-
): void {
17-
if (this.removeEventListener) {
18-
this.removeEventListener()
19-
}
20-
this.removeEventListener = setup(focused => {
21-
if (typeof focused === 'boolean') {
22-
this.setFocused(focused)
23-
} else {
24-
this.onFocus()
1+
import { createEventManager } from './eventManager'
2+
3+
export const createFocusManager = () => {
4+
const { setEventListener, subscribe, ...manager } = createEventManager([
5+
'visibilitychange',
6+
'focus',
7+
])
8+
9+
return {
10+
subscribe,
11+
setEventListener,
12+
setFocused: manager.setValue,
13+
isFocused: (): boolean => {
14+
const value = manager.getValue()
15+
if (typeof value === 'boolean') {
16+
return value
2517
}
26-
})
27-
}
28-
29-
setFocused(focused?: boolean): void {
30-
this.focused = focused
31-
32-
if (focused) {
33-
this.onFocus()
34-
}
35-
}
3618

37-
onFocus(): void {
38-
this.listeners.forEach(listener => {
39-
listener()
40-
})
41-
}
42-
43-
isFocused(): boolean {
44-
if (typeof this.focused === 'boolean') {
45-
return this.focused
46-
}
47-
48-
// document global can be unavailable in react native
49-
if (typeof document === 'undefined') {
50-
return true
51-
}
52-
53-
return [undefined, 'visible', 'prerender'].includes(
54-
document.visibilityState
55-
)
56-
}
57-
58-
private setDefaultEventListener() {
59-
if (!isServer && window?.addEventListener) {
60-
this.setEventListener(onFocus => {
61-
const listener = () => onFocus()
62-
// Listen to visibillitychange and focus
63-
window.addEventListener('visibilitychange', listener, false)
64-
window.addEventListener('focus', listener, false)
19+
// document global can be unavailable in react native
20+
if (typeof document === 'undefined') {
21+
return true
22+
}
6523

66-
return () => {
67-
// Be sure to unsubscribe if a new handler is set
68-
window.removeEventListener('visibilitychange', listener)
69-
window.removeEventListener('focus', listener)
70-
}
71-
})
72-
}
24+
return [undefined, 'visible', 'prerender'].includes(
25+
document.visibilityState
26+
)
27+
},
7328
}
7429
}
7530

76-
export const focusManager = new FocusManager()
31+
export const focusManager = createFocusManager()

src/core/onlineManager.ts

Lines changed: 25 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,31 @@
1-
import { Subscribable } from './subscribable'
2-
import { isServer } from './utils'
3-
4-
class OnlineManager extends Subscribable {
5-
private online?: boolean
6-
private removeEventListener?: () => void
7-
8-
protected onSubscribe(): void {
9-
if (!this.removeEventListener) {
10-
this.setDefaultEventListener()
11-
}
12-
}
13-
14-
setEventListener(
15-
setup: (setOnline: (online?: boolean) => void) => () => void
16-
): void {
17-
if (this.removeEventListener) {
18-
this.removeEventListener()
19-
}
20-
this.removeEventListener = setup((online?: boolean) => {
21-
if (typeof online === 'boolean') {
22-
this.setOnline(online)
23-
} else {
24-
this.onOnline()
1+
import { createEventManager } from './eventManager'
2+
3+
export const createOnlineManager = () => {
4+
const { setEventListener, subscribe, ...manager } = createEventManager([
5+
'online',
6+
'offline',
7+
])
8+
9+
return {
10+
subscribe,
11+
setEventListener,
12+
setOnline: manager.setValue,
13+
isOnline: (): boolean => {
14+
const value = manager.getValue()
15+
if (typeof value === 'boolean') {
16+
return value
2517
}
26-
})
27-
}
28-
29-
setOnline(online?: boolean): void {
30-
this.online = online
31-
32-
if (online) {
33-
this.onOnline()
34-
}
35-
}
3618

37-
onOnline(): void {
38-
this.listeners.forEach(listener => {
39-
listener()
40-
})
41-
}
42-
43-
isOnline(): boolean {
44-
if (typeof this.online === 'boolean') {
45-
return this.online
46-
}
47-
48-
if (
49-
typeof navigator === 'undefined' ||
50-
typeof navigator.onLine === 'undefined'
51-
) {
52-
return true
53-
}
54-
55-
return navigator.onLine
56-
}
57-
58-
private setDefaultEventListener() {
59-
if (!isServer && window?.addEventListener) {
60-
this.setEventListener(onOnline => {
61-
const listener = () => onOnline()
62-
// Listen to online
63-
window.addEventListener('online', listener, false)
64-
window.addEventListener('offline', listener, false)
19+
if (
20+
typeof navigator === 'undefined' ||
21+
typeof navigator.onLine === 'undefined'
22+
) {
23+
return true
24+
}
6525

66-
return () => {
67-
// Be sure to unsubscribe if a new handler is set
68-
window.removeEventListener('online', listener)
69-
window.removeEventListener('offline', listener)
70-
}
71-
})
72-
}
26+
return navigator.onLine
27+
},
7328
}
7429
}
7530

76-
export const onlineManager = new OnlineManager()
31+
export const onlineManager = createOnlineManager()

src/core/tests/focusManager.test.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { sleep } from '../utils'
2-
import { focusManager } from '../focusManager'
2+
import { createFocusManager } from '../focusManager'
33

44
describe('focusManager', () => {
5-
afterEach(() => {
6-
// Reset removeEventListener private property to avoid side effects between tests
7-
focusManager['removeEventListener'] = undefined
5+
let focusManager: ReturnType<typeof createFocusManager>
6+
beforeEach(() => {
7+
focusManager = createFocusManager()
88
})
99

1010
it('should call previous remove handler when replacing an event listener', () => {
@@ -69,7 +69,7 @@ describe('focusManager', () => {
6969

7070
const setEventListenerSpy = jest.spyOn(focusManager, 'setEventListener')
7171

72-
const unsubscribe = focusManager.subscribe()
72+
const unsubscribe = focusManager.subscribe(() => undefined)
7373
expect(setEventListenerSpy).toHaveBeenCalledTimes(0)
7474

7575
unsubscribe()
@@ -88,7 +88,7 @@ describe('focusManager', () => {
8888
)
8989

9090
// Should set the default event listener with window event listeners
91-
const unsubscribe = focusManager.subscribe()
91+
const unsubscribe = focusManager.subscribe(() => undefined)
9292
expect(addEventListenerSpy).toHaveBeenCalledTimes(2)
9393

9494
// Should replace the window default event listener by a new one

src/core/tests/onlineManager.test.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { onlineManager } from '../onlineManager'
1+
import { createOnlineManager } from '../onlineManager'
22
import { sleep } from '../utils'
33

44
describe('onlineManager', () => {
5-
afterEach(() => {
6-
// Reset removeEventListener private property to avoid side effects between tests
7-
onlineManager['removeEventListener'] = undefined
5+
let onlineManager: ReturnType<typeof createOnlineManager>
6+
beforeEach(() => {
7+
onlineManager = createOnlineManager()
88
})
99

1010
test('isOnline should return true if navigator is undefined', () => {
@@ -64,7 +64,7 @@ describe('onlineManager', () => {
6464

6565
const setEventListenerSpy = jest.spyOn(onlineManager, 'setEventListener')
6666

67-
const unsubscribe = onlineManager.subscribe()
67+
const unsubscribe = onlineManager.subscribe(() => undefined)
6868
expect(setEventListenerSpy).toHaveBeenCalledTimes(0)
6969

7070
unsubscribe()
@@ -83,7 +83,7 @@ describe('onlineManager', () => {
8383
)
8484

8585
// Should set the default event listener with window event listeners
86-
const unsubscribe = onlineManager.subscribe()
86+
const unsubscribe = onlineManager.subscribe(() => undefined)
8787
expect(addEventListenerSpy).toHaveBeenCalledTimes(2)
8888

8989
// Should replace the window default event listener by a new one

0 commit comments

Comments
 (0)