Skip to content

Commit 97ec0a9

Browse files
committed
feat: add sensor/useBattery hook
1 parent d7fca12 commit 97ec0a9

File tree

5 files changed

+246
-2
lines changed

5 files changed

+246
-2
lines changed
+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { storiesOf } from '@storybook/vue'
2+
import { createComponent } from '@vue/composition-api'
3+
import Doc from '../../../__stories__/components/Doc'
4+
import { useBattery } from '../../..'
5+
6+
const Demo = createComponent({
7+
setup() {
8+
const battery = useBattery()
9+
return { battery }
10+
},
11+
render() {
12+
const { battery } = this
13+
14+
if (!battery.isSupported) {
15+
return (
16+
<div>
17+
<strong>Battery sensor</strong>: <span>not supported</span>
18+
</div>
19+
)
20+
}
21+
22+
if (!battery.fetched) {
23+
return (
24+
<div>
25+
<strong>Battery sensor</strong>: <span>supported</span> <br />
26+
<strong>Battery state</strong>: <span>fetching</span>
27+
</div>
28+
)
29+
}
30+
31+
return (
32+
<div>
33+
<strong>Battery sensor</strong>:&nbsp;&nbsp; <span>supported</span>{' '}
34+
<br />
35+
<strong>Battery state</strong>: <span>fetched</span> <br />
36+
<strong>Charge level</strong>:&nbsp;&nbsp;{' '}
37+
<span>{(battery.level * 100).toFixed(0)}%</span> <br />
38+
<strong>Charging</strong>:&nbsp;&nbsp;{' '}
39+
<span>{battery.charging ? 'yes' : 'no'}</span> <br />
40+
<strong>Charging time</strong>:&nbsp;&nbsp;
41+
<span>
42+
{battery.chargingTime ? battery.chargingTime : 'finished'}
43+
</span>{' '}
44+
<br />
45+
<strong>Discharging time</strong>:&nbsp;&nbsp;{' '}
46+
<span>{battery.dischargingTime}</span>
47+
</div>
48+
)
49+
}
50+
})
51+
52+
const Docs = () => <Doc md={require('./doc.md')}></Doc>
53+
54+
storiesOf('Sensor|useBattery', module)
55+
.add('Docs', () => Docs as any)
56+
.add('Demo', () => Demo)

src/hooks/sensor/useBattery/doc.md

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# useBattery
2+
3+
Vue hook that tracks device battery state.
4+
5+
> **Note:** current `BatteryManager` API state is obsolete.
6+
> Although it may still work in some browsers, its use is discouraged since it could be removed at any time.
7+
8+
## Usage
9+
10+
```jsx
11+
import { createComponent } from '@vue/composition-api'
12+
import { useBattery } from 'vuses'
13+
14+
const Demo = createComponent({
15+
setup() {
16+
const battery = useBattery()
17+
return { battery }
18+
},
19+
render() {
20+
const { battery } = this
21+
22+
if (!battery.isSupported) {
23+
return (
24+
<div>
25+
<strong>Battery sensor</strong>: <span>not supported</span>
26+
</div>
27+
)
28+
}
29+
30+
if (!battery.fetched) {
31+
return (
32+
<div>
33+
<strong>Battery sensor</strong>: <span>supported</span> <br />
34+
<strong>Battery state</strong>: <span>fetching</span>
35+
</div>
36+
)
37+
}
38+
39+
return (
40+
<div>
41+
<strong>Battery sensor</strong>:&nbsp;&nbsp; <span>supported</span>{' '}
42+
<br />
43+
<strong>Battery state</strong>: <span>fetched</span> <br />
44+
<strong>Charge level</strong>:&nbsp;&nbsp;{' '}
45+
<span>{(battery.level * 100).toFixed(0)}%</span> <br />
46+
<strong>Charging</strong>:&nbsp;&nbsp; <span>
47+
{battery.charging ? 'yes' : 'no'}
48+
</span> <br />
49+
<strong>Charging time</strong>:&nbsp;&nbsp;
50+
<span>
51+
{battery.chargingTime ? battery.chargingTime : 'finished'}
52+
</span> <br />
53+
<strong>Discharging time</strong>:&nbsp;&nbsp;{' '}
54+
<span>{battery.dischargingTime}</span>
55+
</div>
56+
)
57+
}
58+
})
59+
```
60+
61+
## Reference
62+
63+
```typescript
64+
interface BatteryState {
65+
charging: boolean
66+
chargingTime: number
67+
dischargingTime: number
68+
level: number
69+
}
70+
71+
type FetchedBatteryState = BatteryState & {
72+
isSupported: boolean
73+
fetched: boolean
74+
}
75+
76+
type UseBatteryState =
77+
| { isSupported: false } // Battery API is not supported
78+
| FetchedBatteryState // battery API supported
79+
80+
function useBattery(): UseBatteryState
81+
```
82+
83+
## ReturnValue
84+
85+
- **`isSupported`**_`: boolean`_ - whether browser/devise supports BatteryManager;
86+
- **`fetched`**_`: boolean`_ - whether battery state is fetched;
87+
- **`level`**_`: number`_ - representing the system's battery charge level scaled to a value between 0.0 and 1.0.
88+
- **`charging`**_`: boolean`_ - indicating whether or not the battery is currently being charged.
89+
- **`dischargingTime`**_`: number`_ - remaining time in seconds until the battery is completely discharged and the system will suspend.
90+
- **`chargingTime`**_`: number`_ - remaining time in seconds until the battery is fully charged, or 0 if the battery is already fully charged.

src/hooks/sensor/useBattery/index.ts

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { reactive, onMounted, onUnmounted } from '@vue/composition-api'
2+
import {
3+
checkBrowser,
4+
addEventListener,
5+
removeEventListener,
6+
warn
7+
} from '../../../utils'
8+
9+
export interface BatteryState {
10+
charging: boolean
11+
chargingTime: number
12+
dischargingTime: number
13+
level: number
14+
}
15+
16+
type FetchedBatteryState = BatteryState & {
17+
isSupported: boolean
18+
fetched: boolean
19+
}
20+
21+
type UseBatteryState =
22+
| { isSupported: false } // Battery API is not supported
23+
| FetchedBatteryState // battery API supported
24+
25+
export interface BatteryManager extends Readonly<BatteryState>, EventTarget {
26+
onchargingchange: VoidFunction
27+
onchargingtimechange: VoidFunction
28+
ondischargingtimechange: VoidFunction
29+
onlevelchange: VoidFunction
30+
}
31+
32+
interface NavigatorWithPossibleBattery extends Navigator {
33+
getBattery?: () => Promise<BatteryManager>
34+
}
35+
36+
const nav: NavigatorWithPossibleBattery | undefined =
37+
typeof navigator === 'object' ? navigator : undefined
38+
const isBatteryApiSupported = nav && typeof nav.getBattery === 'function'
39+
40+
export default function useBattery(): UseBatteryState {
41+
checkBrowser(useBattery.name)
42+
43+
if (!isBatteryApiSupported) {
44+
return { isSupported: false }
45+
}
46+
47+
const state = reactive<FetchedBatteryState>({
48+
isSupported: true,
49+
fetched: false,
50+
charging: false,
51+
chargingTime: 0,
52+
dischargingTime: 0,
53+
level: 0
54+
})
55+
56+
let mounted = true
57+
let battery: BatteryManager | null = null
58+
59+
const update = () => {
60+
if (!mounted || !battery) {
61+
return
62+
}
63+
state.isSupported = true
64+
state.fetched = true
65+
state.level = battery.level
66+
state.charging = battery.charging
67+
state.dischargingTime = battery.dischargingTime
68+
state.chargingTime = battery.chargingTime
69+
}
70+
71+
onMounted(() => {
72+
nav!.getBattery!()
73+
.then((bm: BatteryManager) => {
74+
if (!mounted) return
75+
battery = bm
76+
addEventListener(battery, 'chargingchange', update)
77+
addEventListener(battery, 'chargingtimechange', update)
78+
addEventListener(battery, 'dischargingtimechange', update)
79+
addEventListener(battery, 'levelchange', update)
80+
update()
81+
})
82+
.catch(warn)
83+
})
84+
85+
onUnmounted(() => {
86+
mounted = false
87+
if (battery) {
88+
removeEventListener(battery, 'chargingchange', update)
89+
removeEventListener(battery, 'chargingtimechange', update)
90+
removeEventListener(battery, 'dischargingtimechange', update)
91+
removeEventListener(battery, 'levelchange', update)
92+
}
93+
})
94+
95+
return state
96+
}

src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export { default as useWindowSize } from './hooks/sensor/useWindowSize'
1010
export { default as useWindowScroll } from './hooks/sensor/useWindowScroll'
1111
export { default as useGeolocation } from './hooks/sensor/useGeolocation'
1212
export { default as useMouse } from './hooks/sensor/useMouse'
13+
export { default as useBattery } from './hooks/sensor/useBattery'
1314

1415
// Side Effect Hooks
1516
export { default as useTitle } from './hooks/sideEffect/useTitle'

src/utils/index.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { isRef, Ref } from '@vue/composition-api'
2+
import { BatteryManager } from '../hooks/sensor/useBattery'
23

34
export const isBrowser = typeof window === 'object'
45

@@ -34,7 +35,7 @@ export const checkBrowser = (ctx = '') => {
3435
}
3536

3637
export const addEventListener = (
37-
el: Element | Window | PermissionStatus | Document,
38+
el: Element | Window | PermissionStatus | Document | BatteryManager,
3839
event: string,
3940
handler: EventListener,
4041
options?: AddEventListenerOptions
@@ -43,7 +44,7 @@ export const addEventListener = (
4344
}
4445

4546
export const removeEventListener = (
46-
el: Element | Window | PermissionStatus | Document,
47+
el: Element | Window | PermissionStatus | Document | BatteryManager,
4748
event: string,
4849
handler: EventListener,
4950
options?: EventListenerOptions

0 commit comments

Comments
 (0)