@symbiote-native/battery 3.0.1 → 3.0.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.
Files changed (50) hide show
  1. package/README.md +53 -38
  2. package/build/angular/index.d.ts +1 -0
  3. package/build/angular/index.js +1 -0
  4. package/build/angular/services/power-state.service/index.d.ts +6 -0
  5. package/build/angular/services/power-state.service/index.js +60 -0
  6. package/build/core/index.d.ts +1 -0
  7. package/build/core/index.js +1 -0
  8. package/build/core/power-state.d.ts +3 -0
  9. package/build/core/power-state.js +37 -0
  10. package/build/react/hooks/use-power-state/index.d.ts +2 -0
  11. package/build/react/hooks/use-power-state/index.js +8 -0
  12. package/build/react/index.d.ts +1 -0
  13. package/build/react/index.js +1 -0
  14. package/build/solid/index.d.ts +1 -0
  15. package/build/solid/index.js +1 -0
  16. package/build/solid/primitives/create-power-state.d.ts +3 -0
  17. package/build/solid/primitives/create-power-state.js +8 -0
  18. package/build/svelte/index.d.ts +1 -0
  19. package/build/svelte/index.js +1 -0
  20. package/build/svelte/runes/use-power-state.svelte.d.ts +4 -0
  21. package/build/svelte/runes/use-power-state.svelte.js +13 -0
  22. package/build/vue/composables/use-power-state/index.d.ts +3 -0
  23. package/build/vue/composables/use-power-state/index.js +16 -0
  24. package/build/vue/index.d.ts +1 -0
  25. package/build/vue/index.js +1 -0
  26. package/build-ngc/angular/index.d.ts +1 -0
  27. package/build-ngc/angular/index.js +1 -0
  28. package/build-ngc/angular/index.js.map +1 -1
  29. package/build-ngc/angular/services/power-state.service/index.d.ts +9 -0
  30. package/build-ngc/angular/services/power-state.service/index.js +18 -0
  31. package/build-ngc/angular/services/power-state.service/index.js.map +1 -0
  32. package/build-ngc/core/index.d.ts +1 -0
  33. package/build-ngc/core/index.js +1 -0
  34. package/build-ngc/core/index.js.map +1 -1
  35. package/build-ngc/core/power-state.d.ts +3 -0
  36. package/build-ngc/core/power-state.js +38 -0
  37. package/build-ngc/core/power-state.js.map +1 -0
  38. package/package.json +15 -15
  39. package/src/angular/index.ts +1 -0
  40. package/src/angular/services/power-state.service/index.ts +21 -0
  41. package/src/core/index.ts +1 -0
  42. package/src/core/power-state.ts +63 -0
  43. package/src/react/hooks/use-power-state/index.ts +15 -0
  44. package/src/react/index.ts +1 -0
  45. package/src/solid/index.ts +1 -0
  46. package/src/solid/primitives/create-power-state.ts +15 -0
  47. package/src/svelte/index.ts +1 -0
  48. package/src/svelte/runes/use-power-state.svelte.ts +22 -0
  49. package/src/vue/composables/use-power-state/index.ts +29 -0
  50. package/src/vue/index.ts +1 -0
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @symbiote-native/battery
2
2
 
3
3
  Port of [`expo-battery`](https://docs.expo.dev/versions/latest/sdk/battery/) for
4
- [SymbioteNative](../../README.md) — battery level and state, low-power-mode detection, reachable
4
+ [SymbioteNative](../../README.md) - battery level and state, low-power-mode detection, reachable
5
5
  from every adapter (React, Vue, Svelte, Solid, Angular), not just React.
6
6
 
7
7
  Built the same way as [`@symbiote-native/local-auth`](../local-auth) and
@@ -24,74 +24,74 @@ npx @symbiote-native/cli new my-app --battery
24
24
  npx @symbiote-native/cli add --battery
25
25
  ```
26
26
 
27
- Either way: installs `@symbiote-native/battery` and wires the native autolinking automatically — see
27
+ Either way: installs `@symbiote-native/battery` and wires the native autolinking automatically - see
28
28
  [`@symbiote-native/cli`](../cli).
29
29
 
30
30
  <details>
31
- <summary>Manual install (no CLI — installing and wiring native autolinking by hand)</summary>
31
+ <summary>Manual install (no CLI - installing and wiring native autolinking by hand)</summary>
32
32
 
33
33
  ```bash
34
34
  npm install @symbiote-native/battery
35
35
  ```
36
36
 
37
37
  Depends on `expo-battery` and `expo-modules-core` directly (regular dependencies, pinned to exact
38
- versions — never a caret range, since this package's `core/` is hand-ported against one specific
38
+ versions - never a caret range, since this package's `core/` is hand-ported against one specific
39
39
  native API shape and a newer resolve could silently drift the two apart). Never install
40
- `expo-battery` yourself, and never add the `expo` package to this project — it bundles its own
40
+ `expo-battery` yourself, and never add the `expo` package to this project - it bundles its own
41
41
  Metro/Babel pipeline that conflicts with this project's own.
42
42
 
43
43
  ### Required one-time step: native autolinking wiring
44
44
 
45
45
  Unlike a plain RN native module, `expo-battery`'s native code is discovered by
46
- `expo-modules-autolinking`, not RN's own `react-native.config.cjs` mechanism — this needs wiring
46
+ `expo-modules-autolinking`, not RN's own `react-native.config.cjs` mechanism - this needs wiring
47
47
  into the native host app **once**, covering this package and every other `expo-modules-core`
48
48
  package (`@symbiote-native/sensors`, `@symbiote-native/local-auth`) with zero further changes:
49
49
 
50
50
  | Platform | Touches |
51
51
  | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
52
- | iOS | `ios/Podfile` — add `use_expo_modules!` |
53
- | iOS | `AppDelegate.swift` — Expo's runtime-bootstrap hook |
54
- | Android | `settings.gradle` / `app/build.gradle` — resolve and include the Expo Gradle projects |
55
- | Android | `MainApplication.kt` — Expo's bootstrap hook, plus a hand-written native-module name map (there's no `expo` meta-package here to auto-generate one) |
52
+ | iOS | `ios/Podfile` - add `use_expo_modules!` |
53
+ | iOS | `AppDelegate.swift` - Expo's runtime-bootstrap hook |
54
+ | Android | `settings.gradle` / `app/build.gradle` - resolve and include the Expo Gradle projects |
55
+ | Android | `MainApplication.kt` - Expo's bootstrap hook, plus a hand-written native-module name map (there's no `expo` meta-package here to auto-generate one) |
56
56
 
57
- Full mechanics — the Podfile pieces that normally ship inside the `expo` package, the `expo`
58
- peer-dependency exclusion list — live in the `symbiote-expo-native-module` skill. Reference
57
+ Full mechanics - the Podfile pieces that normally ship inside the `expo` package, the `expo`
58
+ peer-dependency exclusion list - live in the `symbiote-expo-native-module` skill. Reference
59
59
  implementation: `examples/expo-react/ios/Podfile` and
60
60
  `examples/expo-react/android/app/src/main/java/com/canaryexpo/MainApplication.kt`.
61
61
 
62
62
  </details>
63
63
 
64
- No platform permission string is needed for battery's basic surface —
64
+ No platform permission string is needed for battery's basic surface:
65
65
  `isBatteryOptimizationEnabledAsync` reads an Android-only system state with no runtime permission
66
66
  prompt either.
67
67
 
68
68
  ## Shape
69
69
 
70
70
  ```
71
- src/core/ Battery.ts — one-shot get*Async functions + addListener subscriptions.
71
+ src/core/ Battery.ts - one-shot get*Async functions + addListener subscriptions.
72
72
  native-module.ts resolves the native module through expo-modules-core's
73
- requireNativeModule. types.ts — BatteryState enum, PowerState,
73
+ requireNativeModule. types.ts - BatteryState enum, PowerState,
74
74
  *Event shapes, hand-ported from Battery.types.ts.
75
- src/react/hooks/ @symbiote-native/battery/react — useBatteryLevel, useBatteryState,
76
- useLowPowerMode
77
- src/vue/composables/ @symbiote-native/battery/vue — same three names, Vue lifecycle
78
- src/svelte/runes/ @symbiote-native/battery/svelte — same three names, read as `.current`
79
- src/solid/primitives/ @symbiote-native/battery/solid — createBatteryLevel, createBatteryState,
80
- createLowPowerMode (each returns an Accessor)
81
- src/angular/services/ @symbiote-native/battery/angular — BatteryLevelService, BatteryStateService,
82
- LowPowerModeService (`.connect()` returns a Signal)
75
+ src/react/hooks/ @symbiote-native/battery/react - useBatteryLevel, useBatteryState,
76
+ useLowPowerMode, usePowerState
77
+ src/vue/composables/ @symbiote-native/battery/vue - same four names, Vue lifecycle
78
+ src/svelte/runes/ @symbiote-native/battery/svelte - same four names, read as `.current`
79
+ src/solid/primitives/ @symbiote-native/battery/solid - createBatteryLevel, createBatteryState,
80
+ createLowPowerMode, createPowerState (each returns an Accessor)
81
+ src/angular/services/ @symbiote-native/battery/angular - BatteryLevelService, BatteryStateService,
82
+ LowPowerModeService, PowerStateService (`.connect()` returns a Signal)
83
83
  ```
84
84
 
85
85
  Each adapter's hook/composable/rune/primitive/service is a thin lifecycle wrapper (seed from the
86
86
  one-shot `get*Async` call, subscribe to the matching listener, unsubscribe on unmount/cleanup)
87
- over the same `core` functions — the subscription and fallback logic is written once and shared
87
+ over the same `core` functions - the subscription and fallback logic is written once and shared
88
88
  by every adapter. Solid's naming differs on purpose: `create*`, not `use*`, which Solid reserves
89
89
  for consuming something that already exists.
90
90
 
91
91
  ## Use it
92
92
 
93
93
  ```tsx
94
- // React — examples/expo-react/screens/BatteryScreen.tsx
94
+ // React - examples/expo-react/screens/BatteryScreen.tsx
95
95
  import {
96
96
  useBatteryLevel,
97
97
  useBatteryState,
@@ -120,7 +120,7 @@ function BatteryScreen() {
120
120
  ```
121
121
 
122
122
  ```vue
123
- <!-- Vue — examples/expo-vue-sfc/screens/BatteryScreen.vue -->
123
+ <!-- Vue - examples/expo-vue-sfc/screens/BatteryScreen.vue -->
124
124
  <script setup lang="ts">
125
125
  import { computed } from 'vue';
126
126
  import {
@@ -146,7 +146,7 @@ const batteryLevelText = computed(() =>
146
146
  ```
147
147
 
148
148
  ```svelte
149
- <!-- Svelte — examples/expo-svelte/screens/BatteryScreen.svelte. Each rune returns a boxed
149
+ <!-- Svelte - examples/expo-svelte/screens/BatteryScreen.svelte. Each rune returns a boxed
150
150
  getter object, read as `.current`, since a bare `$state` doesn't survive being handed out
151
151
  of a plain function. -->
152
152
  <script lang="ts">
@@ -176,7 +176,7 @@ const batteryLevelText = computed(() =>
176
176
  ```
177
177
 
178
178
  ```tsx
179
- // Solid — an accessor per value; call it to read, so a component body that runs once still
179
+ // Solid - an accessor per value; call it to read, so a component body that runs once still
180
180
  // re-renders the leaf that reads it.
181
181
  import {
182
182
  createBatteryLevel,
@@ -207,7 +207,7 @@ function BatteryScreen() {
207
207
  ```
208
208
 
209
209
  ```ts
210
- // Angular — examples/expo-angular/src/screens/BatteryScreen.ts
210
+ // Angular - examples/expo-angular/src/screens/BatteryScreen.ts
211
211
  import { Component, inject } from '@angular/core';
212
212
  import {
213
213
  BatteryLevelService,
@@ -230,7 +230,7 @@ export class BatteryScreen {
230
230
 
231
231
  Each of the demo screens above also renders a one-shot capabilities card
232
232
  (`isAvailableAsync()`, and Android-only `isBatteryOptimizationEnabledAsync()`) resolved directly
233
- from `@symbiote-native/battery`'s core entry point — see the linked files for the full version.
233
+ from `@symbiote-native/battery`'s core entry point - see the linked files for the full version.
234
234
  The iOS Simulator reports the battery API as unavailable (`isAvailableAsync()` resolves `false`)
235
235
  since simulators have no real battery hardware; a physical device is needed to see live readings.
236
236
 
@@ -238,9 +238,9 @@ since simulators have no real battery hardware; a physical device is needed to s
238
238
 
239
239
  A mix of stateless async functions and three listener-based subscriptions
240
240
  (`addBatteryLevelListener`/`addBatteryStateListener`/`addLowPowerModeListener`), each with its
241
- own adapter-level lifecycle hook — one hook/composable/service per listener, matching upstream's
242
- own `useBatteryLevel`/`useBatteryState`/`useLowPowerMode` being three separate hooks, not one
243
- combined hook.
241
+ own adapter-level lifecycle hook, matching upstream's `useBatteryLevel`/`useBatteryState`/
242
+ `useLowPowerMode`. `usePowerState` merges all three into one `PowerState` value; the seed and
243
+ merge logic is `watchPowerState` in core, so every adapter only holds the state.
244
244
 
245
245
  ```ts
246
246
  isAvailableAsync(): Promise<boolean> // battery API availability on this device
@@ -255,7 +255,7 @@ addLowPowerModeListener(listener): EventSubscription // fires when low
255
255
  ```
256
256
 
257
257
  Plus `BatteryState` (enum), `PowerState`, `BatteryLevelEvent`, `BatteryStateEvent`,
258
- `PowerModeEvent` — ported from upstream's `Battery.types.ts`.
258
+ `PowerModeEvent` - ported from upstream's `Battery.types.ts`.
259
259
 
260
260
  ```ts
261
261
  import {
@@ -269,42 +269,57 @@ import {
269
269
  useBatteryLevel,
270
270
  useBatteryState,
271
271
  useLowPowerMode,
272
+ usePowerState,
272
273
  } from '@symbiote-native/battery/react';
273
274
  import {
274
275
  useBatteryLevel,
275
276
  useBatteryState,
276
277
  useLowPowerMode,
278
+ usePowerState,
277
279
  } from '@symbiote-native/battery/vue';
278
280
  import {
279
281
  useBatteryLevel,
280
282
  useBatteryState,
281
283
  useLowPowerMode,
284
+ usePowerState,
282
285
  } from '@symbiote-native/battery/svelte';
283
286
  import {
284
287
  createBatteryLevel,
285
288
  createBatteryState,
286
289
  createLowPowerMode,
290
+ createPowerState,
287
291
  } from '@symbiote-native/battery/solid';
288
292
  import {
289
293
  BatteryLevelService,
290
294
  BatteryStateService,
291
295
  LowPowerModeService,
296
+ PowerStateService,
292
297
  } from '@symbiote-native/battery/angular';
293
298
  ```
294
299
 
295
300
  Each hook/composable/service seeds its initial value from the matching one-shot
296
301
  `get*Async`/`is*Async` call, then subscribes to the matching listener for updates, and
297
- unsubscribes on unmount — mirroring upstream's own `useBatteryLevel`/`useBatteryState`/
302
+ unsubscribes on unmount - mirroring upstream's own `useBatteryLevel`/`useBatteryState`/
298
303
  `useLowPowerMode`.
299
304
 
305
+ ## Common questions
306
+
307
+ - **Level reads `-1`.** Unknown level (simulator, unsupported device): check for `-1` before
308
+ formatting and do not treat it as low battery.
309
+ - **Live updates?** Read the initial level, then subscribe to the listener and clean up on unmount.
310
+ - **Low Power Mode?** Use the low power mode query and listener to reduce background work.
311
+
312
+ Sources: [Expo docs: Battery](https://docs.expo.dev/versions/latest/sdk/battery/),
313
+ [Battery -100% bug report](https://github.com/karlgroves/bugrout/issues/202).
314
+
300
315
  ## Test it
301
316
 
302
- No Fabric/Descriptor angle at all — battery is a pure async-function + `EventEmitter` listener
317
+ No Fabric/Descriptor angle at all - battery is a pure async-function + `EventEmitter` listener
303
318
  surface, never a view. Tests inject a fake native-module object in place of the real
304
319
  `requireNativeModule` resolution (`src/core/battery.test.ts`,
305
320
  `src/{react,vue,svelte,solid,angular}/**/*.test.{ts,tsx}`, `vitest`), the same pattern
306
- `@symbiote-native/sensors` and `@symbiote-native/local-auth` use — no `installFabric()`, no
307
- ViewConfig. Native rendering itself is verified on-device — see the parent
321
+ `@symbiote-native/sensors` and `@symbiote-native/local-auth` use - no `installFabric()`, no
322
+ ViewConfig. Native rendering itself is verified on-device - see the parent
308
323
  [README](../../README.md).
309
324
 
310
325
  The Android/iOS native wiring is done across all six `examples/expo-*` canary apps
@@ -1,4 +1,5 @@
1
1
  export { BatteryLevelService } from './services/battery-level.service';
2
2
  export { BatteryStateService } from './services/battery-state.service';
3
3
  export { LowPowerModeService } from './services/low-power-mode.service';
4
+ export { PowerStateService } from './services/power-state.service';
4
5
  export * from '../core';
@@ -5,4 +5,5 @@
5
5
  export { BatteryLevelService } from './services/battery-level.service/index.js';
6
6
  export { BatteryStateService } from './services/battery-state.service/index.js';
7
7
  export { LowPowerModeService } from './services/low-power-mode.service/index.js';
8
+ export { PowerStateService } from './services/power-state.service/index.js';
8
9
  export * from '../core/index.js';
@@ -0,0 +1,6 @@
1
+ import { type Signal } from '@angular/core';
2
+ import { type PowerState } from '../../../core';
3
+ export declare class PowerStateService {
4
+ private readonly injector;
5
+ connect(): Signal<PowerState>;
6
+ }
@@ -0,0 +1,60 @@
1
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
2
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
3
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
4
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
5
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
6
+ var _, done = false;
7
+ for (var i = decorators.length - 1; i >= 0; i--) {
8
+ var context = {};
9
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
10
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
11
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
12
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
13
+ if (kind === "accessor") {
14
+ if (result === void 0) continue;
15
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
16
+ if (_ = accept(result.get)) descriptor.get = _;
17
+ if (_ = accept(result.set)) descriptor.set = _;
18
+ if (_ = accept(result.init)) initializers.unshift(_);
19
+ }
20
+ else if (_ = accept(result)) {
21
+ if (kind === "field") initializers.unshift(_);
22
+ else descriptor[key] = _;
23
+ }
24
+ }
25
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
26
+ done = true;
27
+ };
28
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
29
+ var useValue = arguments.length > 2;
30
+ for (var i = 0; i < initializers.length; i++) {
31
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
32
+ }
33
+ return useValue ? value : void 0;
34
+ };
35
+ import { inject, Injectable, Injector } from '@angular/core';
36
+ import { connectWatchedSignal } from '@symbiote-native/angular';
37
+ import { initialPowerState, watchPowerState, } from '../../../core/index.js';
38
+ // Angular twin of `usePowerState`, same `connect()` pattern as `BatteryLevelService`
39
+ let PowerStateService = (() => {
40
+ let _classDecorators = [Injectable({ providedIn: 'root' })];
41
+ let _classDescriptor;
42
+ let _classExtraInitializers = [];
43
+ let _classThis;
44
+ var PowerStateService = class {
45
+ static { _classThis = this; }
46
+ static {
47
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
48
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
49
+ PowerStateService = _classThis = _classDescriptor.value;
50
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
51
+ __runInitializers(_classThis, _classExtraInitializers);
52
+ }
53
+ injector = inject(Injector);
54
+ connect() {
55
+ return connectWatchedSignal(this.injector, initialPowerState, watchPowerState);
56
+ }
57
+ };
58
+ return PowerStateService = _classThis;
59
+ })();
60
+ export { PowerStateService };
@@ -1,3 +1,4 @@
1
1
  export { isAvailableAsync, getBatteryLevelAsync, getBatteryStateAsync, isLowPowerModeEnabledAsync, isBatteryOptimizationEnabledAsync, getPowerStateAsync, addBatteryLevelListener, addBatteryStateListener, addLowPowerModeListener, } from './battery';
2
2
  export { BatteryState, type PowerState, type BatteryLevelEvent, type BatteryStateEvent, type PowerModeEvent, } from './types';
3
+ export { initialPowerState, watchPowerState } from './power-state';
3
4
  export type { EventSubscription } from 'expo-modules-core';
@@ -1,2 +1,3 @@
1
1
  export { isAvailableAsync, getBatteryLevelAsync, getBatteryStateAsync, isLowPowerModeEnabledAsync, isBatteryOptimizationEnabledAsync, getPowerStateAsync, addBatteryLevelListener, addBatteryStateListener, addLowPowerModeListener, } from './battery.js';
2
2
  export { BatteryState, } from './types.js';
3
+ export { initialPowerState, watchPowerState } from './power-state.js';
@@ -0,0 +1,3 @@
1
+ import { type PowerState } from './types';
2
+ export declare const initialPowerState: PowerState;
3
+ export declare function watchPowerState(onChange: (state: PowerState) => void): () => void;
@@ -0,0 +1,37 @@
1
+ // Shared half of upstream's `usePowerState`: level, state and low-power mode as one merged value
2
+ import { addBatteryLevelListener, addBatteryStateListener, addLowPowerModeListener, getBatteryLevelAsync, getBatteryStateAsync, isLowPowerModeEnabledAsync, } from './battery.js';
3
+ import { BatteryState } from './types.js';
4
+ export const initialPowerState = {
5
+ lowPowerMode: false,
6
+ batteryLevel: -1,
7
+ batteryState: BatteryState.UNKNOWN,
8
+ };
9
+ // Seeds each field with a one-shot fetch, then follows native events; returns the teardown
10
+ export function watchPowerState(onChange) {
11
+ let state = initialPowerState;
12
+ let isStopped = false;
13
+ const heardFields = new Set();
14
+ // A native event that lands before its seed resolves wins because the seed is the older value
15
+ function write(key, value, isSeed) {
16
+ if (isStopped || (isSeed && heardFields.has(key))) {
17
+ return;
18
+ }
19
+ if (!isSeed) {
20
+ heardFields.add(key);
21
+ }
22
+ state = { ...state, [key]: value };
23
+ onChange(state);
24
+ }
25
+ const subscriptions = [
26
+ addLowPowerModeListener(event => write('lowPowerMode', event.lowPowerMode, false)),
27
+ addBatteryLevelListener(event => write('batteryLevel', event.batteryLevel, false)),
28
+ addBatteryStateListener(event => write('batteryState', event.batteryState, false)),
29
+ ];
30
+ isLowPowerModeEnabledAsync().then(value => write('lowPowerMode', value, true));
31
+ getBatteryLevelAsync().then(value => write('batteryLevel', value, true));
32
+ getBatteryStateAsync().then(value => write('batteryState', value, true));
33
+ return () => {
34
+ isStopped = true;
35
+ subscriptions.forEach(subscription => subscription.remove());
36
+ };
37
+ }
@@ -0,0 +1,2 @@
1
+ import { type PowerState } from '../../../core';
2
+ export declare function usePowerState(): PowerState;
@@ -0,0 +1,8 @@
1
+ // React lifecycle wiring over `watchPowerState` from core, twin of upstream's `usePowerState`
2
+ import { useEffect, useState } from 'react';
3
+ import { initialPowerState, watchPowerState, } from '../../../core/index.js';
4
+ export function usePowerState() {
5
+ const [powerState, setPowerState] = useState(initialPowerState);
6
+ useEffect(() => watchPowerState(setPowerState), []);
7
+ return powerState;
8
+ }
@@ -1,4 +1,5 @@
1
1
  export { useBatteryLevel } from './hooks/use-battery-level';
2
2
  export { useBatteryState } from './hooks/use-battery-state';
3
3
  export { useLowPowerMode } from './hooks/use-low-power-mode';
4
+ export { usePowerState } from './hooks/use-power-state';
4
5
  export * from '../core';
@@ -6,4 +6,5 @@
6
6
  export { useBatteryLevel } from './hooks/use-battery-level/index.js';
7
7
  export { useBatteryState } from './hooks/use-battery-state/index.js';
8
8
  export { useLowPowerMode } from './hooks/use-low-power-mode/index.js';
9
+ export { usePowerState } from './hooks/use-power-state/index.js';
9
10
  export * from '../core/index.js';
@@ -1,4 +1,5 @@
1
1
  export { createBatteryLevel } from './primitives/create-battery-level';
2
2
  export { createBatteryState } from './primitives/create-battery-state';
3
3
  export { createLowPowerMode } from './primitives/create-low-power-mode';
4
+ export { createPowerState } from './primitives/create-power-state';
4
5
  export * from '../core';
@@ -7,4 +7,5 @@
7
7
  export { createBatteryLevel } from './primitives/create-battery-level.js';
8
8
  export { createBatteryState } from './primitives/create-battery-state.js';
9
9
  export { createLowPowerMode } from './primitives/create-low-power-mode.js';
10
+ export { createPowerState } from './primitives/create-power-state.js';
10
11
  export * from '../core/index.js';
@@ -0,0 +1,3 @@
1
+ import { type Accessor } from 'solid-js';
2
+ import { type PowerState } from '../../core';
3
+ export declare function createPowerState(): Accessor<PowerState>;
@@ -0,0 +1,8 @@
1
+ // Solid lifecycle wiring over `watchPowerState` from core, twin of upstream's `usePowerState`
2
+ import { createSignal, onCleanup } from 'solid-js';
3
+ import { initialPowerState, watchPowerState, } from '../../core/index.js';
4
+ export function createPowerState() {
5
+ const [powerState, setPowerState] = createSignal(initialPowerState);
6
+ onCleanup(watchPowerState(setPowerState));
7
+ return powerState;
8
+ }
@@ -1,4 +1,5 @@
1
1
  export { useBatteryLevel } from './runes/use-battery-level.svelte';
2
2
  export { useBatteryState } from './runes/use-battery-state.svelte';
3
3
  export { useLowPowerMode } from './runes/use-low-power-mode.svelte';
4
+ export { usePowerState } from './runes/use-power-state.svelte';
4
5
  export * from '../core';
@@ -6,4 +6,5 @@
6
6
  export { useBatteryLevel } from './runes/use-battery-level.svelte';
7
7
  export { useBatteryState } from './runes/use-battery-state.svelte';
8
8
  export { useLowPowerMode } from './runes/use-low-power-mode.svelte';
9
+ export { usePowerState } from './runes/use-power-state.svelte';
9
10
  export * from '../core/index.js';
@@ -0,0 +1,4 @@
1
+ import { type PowerState } from '../../core';
2
+ export declare function usePowerState(): {
3
+ readonly current: PowerState;
4
+ };
@@ -0,0 +1,13 @@
1
+ // Svelte lifecycle wiring over `watchPowerState` from core, twin of upstream's `usePowerState`
2
+ import { initialPowerState, watchPowerState, } from '../../core/index.js';
3
+ export function usePowerState() {
4
+ let powerState = $state.raw(initialPowerState);
5
+ $effect(() => watchPowerState(next => {
6
+ powerState = next;
7
+ }));
8
+ return {
9
+ get current() {
10
+ return powerState;
11
+ },
12
+ };
13
+ }
@@ -0,0 +1,3 @@
1
+ import { type Ref } from '@vue/runtime-core';
2
+ import { type PowerState } from '../../../core';
3
+ export declare function usePowerState(): Ref<PowerState>;
@@ -0,0 +1,16 @@
1
+ // Vue lifecycle wiring over `watchPowerState` from core, twin of upstream's `usePowerState`
2
+ import { onMounted, onUnmounted, shallowRef, } from '@vue/runtime-core';
3
+ import { initialPowerState, watchPowerState, } from '../../../core/index.js';
4
+ export function usePowerState() {
5
+ const powerState = shallowRef(initialPowerState);
6
+ let stop;
7
+ onMounted(() => {
8
+ stop = watchPowerState(next => {
9
+ powerState.value = next;
10
+ });
11
+ });
12
+ onUnmounted(() => {
13
+ stop?.();
14
+ });
15
+ return powerState;
16
+ }
@@ -1,4 +1,5 @@
1
1
  export { useBatteryLevel } from './composables/use-battery-level';
2
2
  export { useBatteryState } from './composables/use-battery-state';
3
3
  export { useLowPowerMode } from './composables/use-low-power-mode';
4
+ export { usePowerState } from './composables/use-power-state';
4
5
  export * from '../core';
@@ -6,4 +6,5 @@
6
6
  export { useBatteryLevel } from './composables/use-battery-level/index.js';
7
7
  export { useBatteryState } from './composables/use-battery-state/index.js';
8
8
  export { useLowPowerMode } from './composables/use-low-power-mode/index.js';
9
+ export { usePowerState } from './composables/use-power-state/index.js';
9
10
  export * from '../core/index.js';
@@ -1,4 +1,5 @@
1
1
  export { BatteryLevelService } from './services/battery-level.service';
2
2
  export { BatteryStateService } from './services/battery-state.service';
3
3
  export { LowPowerModeService } from './services/low-power-mode.service';
4
+ export { PowerStateService } from './services/power-state.service';
4
5
  export * from '../core';
@@ -5,5 +5,6 @@
5
5
  export { BatteryLevelService } from './services/battery-level.service';
6
6
  export { BatteryStateService } from './services/battery-state.service';
7
7
  export { LowPowerModeService } from './services/low-power-mode.service';
8
+ export { PowerStateService } from './services/power-state.service';
8
9
  export * from '../core';
9
10
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/angular/index.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,yFAAyF;AACzF,yFAAyF;AACzF,8EAA8E;AAC9E,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AACxE,cAAc,SAAS,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/angular/index.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,yFAAyF;AACzF,yFAAyF;AACzF,8EAA8E;AAC9E,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,cAAc,SAAS,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { type Signal } from '@angular/core';
2
+ import { type PowerState } from '../../../core';
3
+ import * as i0 from "@angular/core";
4
+ export declare class PowerStateService {
5
+ private readonly injector;
6
+ connect(): Signal<PowerState>;
7
+ static ɵfac: i0.ɵɵFactoryDeclaration<PowerStateService, never>;
8
+ static ɵprov: i0.ɵɵInjectableDeclaration<PowerStateService>;
9
+ }
@@ -0,0 +1,18 @@
1
+ import { inject, Injectable, Injector } from '@angular/core';
2
+ import { connectWatchedSignal } from '@symbiote-native/angular';
3
+ import { initialPowerState, watchPowerState, } from '../../../core';
4
+ import * as i0 from "@angular/core";
5
+ // Angular twin of `usePowerState`, same `connect()` pattern as `BatteryLevelService`
6
+ export class PowerStateService {
7
+ injector = inject(Injector);
8
+ connect() {
9
+ return connectWatchedSignal(this.injector, initialPowerState, watchPowerState);
10
+ }
11
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PowerStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
12
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PowerStateService, providedIn: 'root' });
13
+ }
14
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PowerStateService, decorators: [{
15
+ type: Injectable,
16
+ args: [{ providedIn: 'root' }]
17
+ }] });
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/angular/services/power-state.service/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAe,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EACL,iBAAiB,EACjB,eAAe,GAEhB,MAAM,eAAe,CAAC;;AAEvB,qFAAqF;AAErF,MAAM,OAAO,iBAAiB;IACX,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAE7C,OAAO;QACL,OAAO,oBAAoB,CACzB,IAAI,CAAC,QAAQ,EACb,iBAAiB,EACjB,eAAe,CAChB,CAAC;IACJ,CAAC;uGATU,iBAAiB;2GAAjB,iBAAiB,cADJ,MAAM;;2FACnB,iBAAiB;kBAD7B,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE"}
@@ -1,3 +1,4 @@
1
1
  export { isAvailableAsync, getBatteryLevelAsync, getBatteryStateAsync, isLowPowerModeEnabledAsync, isBatteryOptimizationEnabledAsync, getPowerStateAsync, addBatteryLevelListener, addBatteryStateListener, addLowPowerModeListener, } from './battery';
2
2
  export { BatteryState, type PowerState, type BatteryLevelEvent, type BatteryStateEvent, type PowerModeEvent, } from './types';
3
+ export { initialPowerState, watchPowerState } from './power-state';
3
4
  export type { EventSubscription } from 'expo-modules-core';
@@ -1,3 +1,4 @@
1
1
  export { isAvailableAsync, getBatteryLevelAsync, getBatteryStateAsync, isLowPowerModeEnabledAsync, isBatteryOptimizationEnabledAsync, getPowerStateAsync, addBatteryLevelListener, addBatteryStateListener, addLowPowerModeListener, } from './battery';
2
2
  export { BatteryState, } from './types';
3
+ export { initialPowerState, watchPowerState } from './power-state';
3
4
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACpB,0BAA0B,EAC1B,iCAAiC,EACjC,kBAAkB,EAClB,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,YAAY,GAKb,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACpB,0BAA0B,EAC1B,iCAAiC,EACjC,kBAAkB,EAClB,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,YAAY,GAKb,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type PowerState } from './types';
2
+ export declare const initialPowerState: PowerState;
3
+ export declare function watchPowerState(onChange: (state: PowerState) => void): () => void;
@@ -0,0 +1,38 @@
1
+ // Shared half of upstream's `usePowerState`: level, state and low-power mode as one merged value
2
+ import { addBatteryLevelListener, addBatteryStateListener, addLowPowerModeListener, getBatteryLevelAsync, getBatteryStateAsync, isLowPowerModeEnabledAsync, } from './battery';
3
+ import { BatteryState } from './types';
4
+ export const initialPowerState = {
5
+ lowPowerMode: false,
6
+ batteryLevel: -1,
7
+ batteryState: BatteryState.UNKNOWN,
8
+ };
9
+ // Seeds each field with a one-shot fetch, then follows native events; returns the teardown
10
+ export function watchPowerState(onChange) {
11
+ let state = initialPowerState;
12
+ let isStopped = false;
13
+ const heardFields = new Set();
14
+ // A native event that lands before its seed resolves wins because the seed is the older value
15
+ function write(key, value, isSeed) {
16
+ if (isStopped || (isSeed && heardFields.has(key))) {
17
+ return;
18
+ }
19
+ if (!isSeed) {
20
+ heardFields.add(key);
21
+ }
22
+ state = { ...state, [key]: value };
23
+ onChange(state);
24
+ }
25
+ const subscriptions = [
26
+ addLowPowerModeListener(event => write('lowPowerMode', event.lowPowerMode, false)),
27
+ addBatteryLevelListener(event => write('batteryLevel', event.batteryLevel, false)),
28
+ addBatteryStateListener(event => write('batteryState', event.batteryState, false)),
29
+ ];
30
+ isLowPowerModeEnabledAsync().then(value => write('lowPowerMode', value, true));
31
+ getBatteryLevelAsync().then(value => write('batteryLevel', value, true));
32
+ getBatteryStateAsync().then(value => write('batteryState', value, true));
33
+ return () => {
34
+ isStopped = true;
35
+ subscriptions.forEach(subscription => subscription.remove());
36
+ };
37
+ }
38
+ //# sourceMappingURL=power-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"power-state.js","sourceRoot":"","sources":["../../src/core/power-state.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,EACvB,oBAAoB,EACpB,oBAAoB,EACpB,0BAA0B,GAC3B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,YAAY,EAAmB,MAAM,SAAS,CAAC;AAExD,MAAM,CAAC,MAAM,iBAAiB,GAAe;IAC3C,YAAY,EAAE,KAAK;IACnB,YAAY,EAAE,CAAC,CAAC;IAChB,YAAY,EAAE,YAAY,CAAC,OAAO;CACnC,CAAC;AAEF,2FAA2F;AAC3F,MAAM,UAAU,eAAe,CAC7B,QAAqC;IAErC,IAAI,KAAK,GAAG,iBAAiB,CAAC;IAC9B,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,MAAM,WAAW,GAAG,IAAI,GAAG,EAAoB,CAAC;IAEhD,8FAA8F;IAC9F,SAAS,KAAK,CACZ,GAAM,EACN,KAAoB,EACpB,MAAe;QAEf,IAAI,SAAS,IAAI,CAAC,MAAM,IAAI,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAClD,OAAO;QACT,CAAC;QACD,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACvB,CAAC;QACD,KAAK,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC;QACnC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,aAAa,GAAG;QACpB,uBAAuB,CAAC,KAAK,CAAC,EAAE,CAC9B,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,CACjD;QACD,uBAAuB,CAAC,KAAK,CAAC,EAAE,CAC9B,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,CACjD;QACD,uBAAuB,CAAC,KAAK,CAAC,EAAE,CAC9B,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,CACjD;KACF,CAAC;IACF,0BAA0B,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CACxC,KAAK,CAAC,cAAc,EAAE,KAAK,EAAE,IAAI,CAAC,CACnC,CAAC;IACF,oBAAoB,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;IACzE,oBAAoB,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;IAEzE,OAAO,GAAG,EAAE;QACV,SAAS,GAAG,IAAI,CAAC;QACjB,aAAa,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC;IAC/D,CAAC,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbiote-native/battery",
3
- "version": "3.0.1",
3
+ "version": "3.0.3",
4
4
  "description": "expo-battery wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, Svelte, Solid, and Angular adapters. Battery level and state (charging/full/unplugged/unknown), low-power-mode detection, plus event listeners for level and state changes.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -70,7 +70,7 @@
70
70
  },
71
71
  "dependencies": {
72
72
  "expo-battery": "57.0.1",
73
- "expo-modules-core": "57.0.5"
73
+ "expo-modules-core": "57.0.20"
74
74
  },
75
75
  "peerDependencies": {
76
76
  "@angular/core": ">=20",
@@ -80,12 +80,12 @@
80
80
  "solid-js": ">=1.9.0",
81
81
  "svelte": ">=5.56.0",
82
82
  "vue": ">=3.5.0",
83
- "@symbiote-native/angular": "^3.1.1",
84
- "@symbiote-native/engine": "^1.3.0",
85
- "@symbiote-native/react": "^3.0.3",
86
- "@symbiote-native/solid": "^3.0.3",
87
- "@symbiote-native/svelte": "^3.0.3",
88
- "@symbiote-native/vue": "^3.0.3"
83
+ "@symbiote-native/angular": "^3.2.0",
84
+ "@symbiote-native/engine": "^1.5.0",
85
+ "@symbiote-native/react": "^3.2.0",
86
+ "@symbiote-native/solid": "^3.1.0",
87
+ "@symbiote-native/svelte": "^3.1.0",
88
+ "@symbiote-native/vue": "^3.2.0"
89
89
  },
90
90
  "peerDependenciesMeta": {
91
91
  "@symbiote-native/angular": {
@@ -133,13 +133,13 @@
133
133
  "solid-js": "^1.9.14",
134
134
  "svelte": "^5.56.0",
135
135
  "typescript": "~6.0.0",
136
- "@symbiote-native/angular": "3.1.1",
137
- "@symbiote-native/engine": "1.3.0",
138
- "@symbiote-native/react": "3.0.3",
139
- "@symbiote-native/solid": "3.0.3",
140
- "@symbiote-native/svelte": "3.0.3",
141
- "@symbiote-native/test-utils": "0.4.3",
142
- "@symbiote-native/vue": "3.0.3"
136
+ "@symbiote-native/angular": "3.2.0",
137
+ "@symbiote-native/engine": "1.5.0",
138
+ "@symbiote-native/react": "3.2.0",
139
+ "@symbiote-native/solid": "3.1.0",
140
+ "@symbiote-native/svelte": "3.1.0",
141
+ "@symbiote-native/test-utils": "0.4.6",
142
+ "@symbiote-native/vue": "3.2.0"
143
143
  },
144
144
  "scripts": {
145
145
  "typecheck": "tsc --build",
@@ -5,4 +5,5 @@
5
5
  export { BatteryLevelService } from './services/battery-level.service';
6
6
  export { BatteryStateService } from './services/battery-state.service';
7
7
  export { LowPowerModeService } from './services/low-power-mode.service';
8
+ export { PowerStateService } from './services/power-state.service';
8
9
  export * from '../core';
@@ -0,0 +1,21 @@
1
+ import { inject, Injectable, Injector, type Signal } from '@angular/core';
2
+ import { connectWatchedSignal } from '@symbiote-native/angular';
3
+ import {
4
+ initialPowerState,
5
+ watchPowerState,
6
+ type PowerState,
7
+ } from '../../../core';
8
+
9
+ // Angular twin of `usePowerState`, same `connect()` pattern as `BatteryLevelService`
10
+ @Injectable({ providedIn: 'root' })
11
+ export class PowerStateService {
12
+ private readonly injector = inject(Injector);
13
+
14
+ connect(): Signal<PowerState> {
15
+ return connectWatchedSignal(
16
+ this.injector,
17
+ initialPowerState,
18
+ watchPowerState,
19
+ );
20
+ }
21
+ }
package/src/core/index.ts CHANGED
@@ -16,4 +16,5 @@ export {
16
16
  type BatteryStateEvent,
17
17
  type PowerModeEvent,
18
18
  } from './types';
19
+ export { initialPowerState, watchPowerState } from './power-state';
19
20
  export type { EventSubscription } from 'expo-modules-core';
@@ -0,0 +1,63 @@
1
+ // Shared half of upstream's `usePowerState`: level, state and low-power mode as one merged value
2
+ import {
3
+ addBatteryLevelListener,
4
+ addBatteryStateListener,
5
+ addLowPowerModeListener,
6
+ getBatteryLevelAsync,
7
+ getBatteryStateAsync,
8
+ isLowPowerModeEnabledAsync,
9
+ } from './battery';
10
+ import { BatteryState, type PowerState } from './types';
11
+
12
+ export const initialPowerState: PowerState = {
13
+ lowPowerMode: false,
14
+ batteryLevel: -1,
15
+ batteryState: BatteryState.UNKNOWN,
16
+ };
17
+
18
+ // Seeds each field with a one-shot fetch, then follows native events; returns the teardown
19
+ export function watchPowerState(
20
+ onChange: (state: PowerState) => void,
21
+ ): () => void {
22
+ let state = initialPowerState;
23
+ let isStopped = false;
24
+ const heardFields = new Set<keyof PowerState>();
25
+
26
+ // A native event that lands before its seed resolves wins because the seed is the older value
27
+ function write<K extends keyof PowerState>(
28
+ key: K,
29
+ value: PowerState[K],
30
+ isSeed: boolean,
31
+ ): void {
32
+ if (isStopped || (isSeed && heardFields.has(key))) {
33
+ return;
34
+ }
35
+ if (!isSeed) {
36
+ heardFields.add(key);
37
+ }
38
+ state = { ...state, [key]: value };
39
+ onChange(state);
40
+ }
41
+
42
+ const subscriptions = [
43
+ addLowPowerModeListener(event =>
44
+ write('lowPowerMode', event.lowPowerMode, false),
45
+ ),
46
+ addBatteryLevelListener(event =>
47
+ write('batteryLevel', event.batteryLevel, false),
48
+ ),
49
+ addBatteryStateListener(event =>
50
+ write('batteryState', event.batteryState, false),
51
+ ),
52
+ ];
53
+ isLowPowerModeEnabledAsync().then(value =>
54
+ write('lowPowerMode', value, true),
55
+ );
56
+ getBatteryLevelAsync().then(value => write('batteryLevel', value, true));
57
+ getBatteryStateAsync().then(value => write('batteryState', value, true));
58
+
59
+ return () => {
60
+ isStopped = true;
61
+ subscriptions.forEach(subscription => subscription.remove());
62
+ };
63
+ }
@@ -0,0 +1,15 @@
1
+ // React lifecycle wiring over `watchPowerState` from core, twin of upstream's `usePowerState`
2
+ import { useEffect, useState } from 'react';
3
+ import {
4
+ initialPowerState,
5
+ watchPowerState,
6
+ type PowerState,
7
+ } from '../../../core';
8
+
9
+ export function usePowerState(): PowerState {
10
+ const [powerState, setPowerState] = useState(initialPowerState);
11
+
12
+ useEffect(() => watchPowerState(setPowerState), []);
13
+
14
+ return powerState;
15
+ }
@@ -7,4 +7,5 @@
7
7
  export { useBatteryLevel } from './hooks/use-battery-level';
8
8
  export { useBatteryState } from './hooks/use-battery-state';
9
9
  export { useLowPowerMode } from './hooks/use-low-power-mode';
10
+ export { usePowerState } from './hooks/use-power-state';
10
11
  export * from '../core';
@@ -8,4 +8,5 @@
8
8
  export { createBatteryLevel } from './primitives/create-battery-level';
9
9
  export { createBatteryState } from './primitives/create-battery-state';
10
10
  export { createLowPowerMode } from './primitives/create-low-power-mode';
11
+ export { createPowerState } from './primitives/create-power-state';
11
12
  export * from '../core';
@@ -0,0 +1,15 @@
1
+ // Solid lifecycle wiring over `watchPowerState` from core, twin of upstream's `usePowerState`
2
+ import { createSignal, onCleanup, type Accessor } from 'solid-js';
3
+ import {
4
+ initialPowerState,
5
+ watchPowerState,
6
+ type PowerState,
7
+ } from '../../core';
8
+
9
+ export function createPowerState(): Accessor<PowerState> {
10
+ const [powerState, setPowerState] = createSignal(initialPowerState);
11
+
12
+ onCleanup(watchPowerState(setPowerState));
13
+
14
+ return powerState;
15
+ }
@@ -7,4 +7,5 @@
7
7
  export { useBatteryLevel } from './runes/use-battery-level.svelte';
8
8
  export { useBatteryState } from './runes/use-battery-state.svelte';
9
9
  export { useLowPowerMode } from './runes/use-low-power-mode.svelte';
10
+ export { usePowerState } from './runes/use-power-state.svelte';
10
11
  export * from '../core';
@@ -0,0 +1,22 @@
1
+ // Svelte lifecycle wiring over `watchPowerState` from core, twin of upstream's `usePowerState`
2
+ import {
3
+ initialPowerState,
4
+ watchPowerState,
5
+ type PowerState,
6
+ } from '../../core';
7
+
8
+ export function usePowerState(): { readonly current: PowerState } {
9
+ let powerState = $state.raw(initialPowerState);
10
+
11
+ $effect(() =>
12
+ watchPowerState(next => {
13
+ powerState = next;
14
+ }),
15
+ );
16
+
17
+ return {
18
+ get current(): PowerState {
19
+ return powerState;
20
+ },
21
+ };
22
+ }
@@ -0,0 +1,29 @@
1
+ // Vue lifecycle wiring over `watchPowerState` from core, twin of upstream's `usePowerState`
2
+ import {
3
+ onMounted,
4
+ onUnmounted,
5
+ shallowRef,
6
+ type Ref,
7
+ } from '@vue/runtime-core';
8
+ import {
9
+ initialPowerState,
10
+ watchPowerState,
11
+ type PowerState,
12
+ } from '../../../core';
13
+
14
+ export function usePowerState(): Ref<PowerState> {
15
+ const powerState = shallowRef(initialPowerState);
16
+ let stop: (() => void) | undefined;
17
+
18
+ onMounted(() => {
19
+ stop = watchPowerState(next => {
20
+ powerState.value = next;
21
+ });
22
+ });
23
+
24
+ onUnmounted(() => {
25
+ stop?.();
26
+ });
27
+
28
+ return powerState;
29
+ }
package/src/vue/index.ts CHANGED
@@ -7,4 +7,5 @@
7
7
  export { useBatteryLevel } from './composables/use-battery-level';
8
8
  export { useBatteryState } from './composables/use-battery-state';
9
9
  export { useLowPowerMode } from './composables/use-low-power-mode';
10
+ export { usePowerState } from './composables/use-power-state';
10
11
  export * from '../core';