@symbiote-native/sensors 0.1.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.
Files changed (264) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +138 -0
  3. package/build/angular/index.d.ts +16 -0
  4. package/build/angular/index.js +12 -0
  5. package/build/angular/services/accelerometer.service/index.d.ts +6 -0
  6. package/build/angular/services/accelerometer.service/index.js +81 -0
  7. package/build/angular/services/barometer.service/index.d.ts +6 -0
  8. package/build/angular/services/barometer.service/index.js +81 -0
  9. package/build/angular/services/device-motion.service/index.d.ts +6 -0
  10. package/build/angular/services/device-motion.service/index.js +81 -0
  11. package/build/angular/services/gyroscope.service/index.d.ts +6 -0
  12. package/build/angular/services/gyroscope.service/index.js +81 -0
  13. package/build/angular/services/light-sensor.service/index.d.ts +6 -0
  14. package/build/angular/services/light-sensor.service/index.js +81 -0
  15. package/build/angular/services/magnetometer-uncalibrated.service/index.d.ts +6 -0
  16. package/build/angular/services/magnetometer-uncalibrated.service/index.js +81 -0
  17. package/build/angular/services/magnetometer.service/index.d.ts +6 -0
  18. package/build/angular/services/magnetometer.service/index.js +81 -0
  19. package/build/angular/services/pedometer.service/index.d.ts +6 -0
  20. package/build/angular/services/pedometer.service/index.js +76 -0
  21. package/build/core/accelerometer.d.ts +10 -0
  22. package/build/core/accelerometer.js +6 -0
  23. package/build/core/barometer.d.ts +9 -0
  24. package/build/core/barometer.js +6 -0
  25. package/build/core/device-motion.d.ts +40 -0
  26. package/build/core/device-motion.js +21 -0
  27. package/build/core/device-sensor.d.ts +27 -0
  28. package/build/core/device-sensor.js +61 -0
  29. package/build/core/gyroscope.d.ts +10 -0
  30. package/build/core/gyroscope.js +6 -0
  31. package/build/core/index.d.ts +11 -0
  32. package/build/core/index.js +10 -0
  33. package/build/core/light-sensor.d.ts +8 -0
  34. package/build/core/light-sensor.js +6 -0
  35. package/build/core/magnetometer-uncalibrated.d.ts +10 -0
  36. package/build/core/magnetometer-uncalibrated.js +6 -0
  37. package/build/core/magnetometer.d.ts +10 -0
  38. package/build/core/magnetometer.js +6 -0
  39. package/build/core/native/exponent-accelerometer.d.ts +3 -0
  40. package/build/core/native/exponent-accelerometer.js +3 -0
  41. package/build/core/native/exponent-barometer.d.ts +3 -0
  42. package/build/core/native/exponent-barometer.js +3 -0
  43. package/build/core/native/exponent-device-motion.d.ts +7 -0
  44. package/build/core/native/exponent-device-motion.js +3 -0
  45. package/build/core/native/exponent-gyroscope.d.ts +3 -0
  46. package/build/core/native/exponent-gyroscope.js +3 -0
  47. package/build/core/native/exponent-light-sensor/index.android.d.ts +3 -0
  48. package/build/core/native/exponent-light-sensor/index.android.js +3 -0
  49. package/build/core/native/exponent-light-sensor/index.d.ts +1 -0
  50. package/build/core/native/exponent-light-sensor/index.ios.d.ts +3 -0
  51. package/build/core/native/exponent-light-sensor/index.ios.js +6 -0
  52. package/build/core/native/exponent-light-sensor/index.js +5 -0
  53. package/build/core/native/exponent-magnetometer-uncalibrated.d.ts +3 -0
  54. package/build/core/native/exponent-magnetometer-uncalibrated.js +3 -0
  55. package/build/core/native/exponent-magnetometer.d.ts +3 -0
  56. package/build/core/native/exponent-magnetometer.js +3 -0
  57. package/build/core/native/exponent-pedometer.d.ts +10 -0
  58. package/build/core/native/exponent-pedometer.js +3 -0
  59. package/build/core/pedometer.d.ts +10 -0
  60. package/build/core/pedometer.js +44 -0
  61. package/build/react/hooks/use-accelerometer/index.d.ts +2 -0
  62. package/build/react/hooks/use-accelerometer/index.js +17 -0
  63. package/build/react/hooks/use-barometer/index.d.ts +2 -0
  64. package/build/react/hooks/use-barometer/index.js +17 -0
  65. package/build/react/hooks/use-device-motion/index.d.ts +2 -0
  66. package/build/react/hooks/use-device-motion/index.js +17 -0
  67. package/build/react/hooks/use-gyroscope/index.d.ts +2 -0
  68. package/build/react/hooks/use-gyroscope/index.js +17 -0
  69. package/build/react/hooks/use-light-sensor/index.d.ts +2 -0
  70. package/build/react/hooks/use-light-sensor/index.js +17 -0
  71. package/build/react/hooks/use-magnetometer/index.d.ts +2 -0
  72. package/build/react/hooks/use-magnetometer/index.js +17 -0
  73. package/build/react/hooks/use-magnetometer-uncalibrated/index.d.ts +2 -0
  74. package/build/react/hooks/use-magnetometer-uncalibrated/index.js +17 -0
  75. package/build/react/hooks/use-pedometer/index.d.ts +2 -0
  76. package/build/react/hooks/use-pedometer/index.js +14 -0
  77. package/build/react/index.d.ts +16 -0
  78. package/build/react/index.js +14 -0
  79. package/build/vue/composables/use-accelerometer/index.d.ts +3 -0
  80. package/build/vue/composables/use-accelerometer/index.js +25 -0
  81. package/build/vue/composables/use-barometer/index.d.ts +3 -0
  82. package/build/vue/composables/use-barometer/index.js +25 -0
  83. package/build/vue/composables/use-device-motion/index.d.ts +3 -0
  84. package/build/vue/composables/use-device-motion/index.js +25 -0
  85. package/build/vue/composables/use-gyroscope/index.d.ts +3 -0
  86. package/build/vue/composables/use-gyroscope/index.js +25 -0
  87. package/build/vue/composables/use-light-sensor/index.d.ts +3 -0
  88. package/build/vue/composables/use-light-sensor/index.js +25 -0
  89. package/build/vue/composables/use-magnetometer/index.d.ts +3 -0
  90. package/build/vue/composables/use-magnetometer/index.js +25 -0
  91. package/build/vue/composables/use-magnetometer-uncalibrated/index.d.ts +3 -0
  92. package/build/vue/composables/use-magnetometer-uncalibrated/index.js +25 -0
  93. package/build/vue/composables/use-pedometer/index.d.ts +3 -0
  94. package/build/vue/composables/use-pedometer/index.js +20 -0
  95. package/build/vue/index.d.ts +16 -0
  96. package/build/vue/index.js +14 -0
  97. package/build-ngc/angular/index.d.ts +16 -0
  98. package/build-ngc/angular/index.js +13 -0
  99. package/build-ngc/angular/index.js.map +1 -0
  100. package/build-ngc/angular/services/accelerometer.service/index.d.ts +9 -0
  101. package/build-ngc/angular/services/accelerometer.service/index.js +40 -0
  102. package/build-ngc/angular/services/accelerometer.service/index.js.map +1 -0
  103. package/build-ngc/angular/services/barometer.service/index.d.ts +9 -0
  104. package/build-ngc/angular/services/barometer.service/index.js +40 -0
  105. package/build-ngc/angular/services/barometer.service/index.js.map +1 -0
  106. package/build-ngc/angular/services/device-motion.service/index.d.ts +9 -0
  107. package/build-ngc/angular/services/device-motion.service/index.js +40 -0
  108. package/build-ngc/angular/services/device-motion.service/index.js.map +1 -0
  109. package/build-ngc/angular/services/gyroscope.service/index.d.ts +9 -0
  110. package/build-ngc/angular/services/gyroscope.service/index.js +40 -0
  111. package/build-ngc/angular/services/gyroscope.service/index.js.map +1 -0
  112. package/build-ngc/angular/services/light-sensor.service/index.d.ts +9 -0
  113. package/build-ngc/angular/services/light-sensor.service/index.js +40 -0
  114. package/build-ngc/angular/services/light-sensor.service/index.js.map +1 -0
  115. package/build-ngc/angular/services/magnetometer-uncalibrated.service/index.d.ts +9 -0
  116. package/build-ngc/angular/services/magnetometer-uncalibrated.service/index.js +40 -0
  117. package/build-ngc/angular/services/magnetometer-uncalibrated.service/index.js.map +1 -0
  118. package/build-ngc/angular/services/magnetometer.service/index.d.ts +9 -0
  119. package/build-ngc/angular/services/magnetometer.service/index.js +40 -0
  120. package/build-ngc/angular/services/magnetometer.service/index.js.map +1 -0
  121. package/build-ngc/angular/services/pedometer.service/index.d.ts +9 -0
  122. package/build-ngc/angular/services/pedometer.service/index.js +35 -0
  123. package/build-ngc/angular/services/pedometer.service/index.js.map +1 -0
  124. package/build-ngc/core/accelerometer.d.ts +10 -0
  125. package/build-ngc/core/accelerometer.js +7 -0
  126. package/build-ngc/core/accelerometer.js.map +1 -0
  127. package/build-ngc/core/barometer.d.ts +9 -0
  128. package/build-ngc/core/barometer.js +7 -0
  129. package/build-ngc/core/barometer.js.map +1 -0
  130. package/build-ngc/core/device-motion.d.ts +40 -0
  131. package/build-ngc/core/device-motion.js +22 -0
  132. package/build-ngc/core/device-motion.js.map +1 -0
  133. package/build-ngc/core/device-sensor.d.ts +27 -0
  134. package/build-ngc/core/device-sensor.js +62 -0
  135. package/build-ngc/core/device-sensor.js.map +1 -0
  136. package/build-ngc/core/gyroscope.d.ts +10 -0
  137. package/build-ngc/core/gyroscope.js +7 -0
  138. package/build-ngc/core/gyroscope.js.map +1 -0
  139. package/build-ngc/core/index.d.ts +11 -0
  140. package/build-ngc/core/index.js +11 -0
  141. package/build-ngc/core/index.js.map +1 -0
  142. package/build-ngc/core/light-sensor.d.ts +8 -0
  143. package/build-ngc/core/light-sensor.js +7 -0
  144. package/build-ngc/core/light-sensor.js.map +1 -0
  145. package/build-ngc/core/magnetometer-uncalibrated.d.ts +10 -0
  146. package/build-ngc/core/magnetometer-uncalibrated.js +7 -0
  147. package/build-ngc/core/magnetometer-uncalibrated.js.map +1 -0
  148. package/build-ngc/core/magnetometer.d.ts +10 -0
  149. package/build-ngc/core/magnetometer.js +7 -0
  150. package/build-ngc/core/magnetometer.js.map +1 -0
  151. package/build-ngc/core/native/exponent-accelerometer.d.ts +3 -0
  152. package/build-ngc/core/native/exponent-accelerometer.js +4 -0
  153. package/build-ngc/core/native/exponent-accelerometer.js.map +1 -0
  154. package/build-ngc/core/native/exponent-barometer.d.ts +3 -0
  155. package/build-ngc/core/native/exponent-barometer.js +4 -0
  156. package/build-ngc/core/native/exponent-barometer.js.map +1 -0
  157. package/build-ngc/core/native/exponent-device-motion.d.ts +7 -0
  158. package/build-ngc/core/native/exponent-device-motion.js +4 -0
  159. package/build-ngc/core/native/exponent-device-motion.js.map +1 -0
  160. package/build-ngc/core/native/exponent-gyroscope.d.ts +3 -0
  161. package/build-ngc/core/native/exponent-gyroscope.js +4 -0
  162. package/build-ngc/core/native/exponent-gyroscope.js.map +1 -0
  163. package/build-ngc/core/native/exponent-light-sensor/index.android.d.ts +3 -0
  164. package/build-ngc/core/native/exponent-light-sensor/index.android.js +4 -0
  165. package/build-ngc/core/native/exponent-light-sensor/index.android.js.map +1 -0
  166. package/build-ngc/core/native/exponent-light-sensor/index.d.ts +1 -0
  167. package/build-ngc/core/native/exponent-light-sensor/index.ios.d.ts +3 -0
  168. package/build-ngc/core/native/exponent-light-sensor/index.ios.js +7 -0
  169. package/build-ngc/core/native/exponent-light-sensor/index.ios.js.map +1 -0
  170. package/build-ngc/core/native/exponent-light-sensor/index.js +6 -0
  171. package/build-ngc/core/native/exponent-light-sensor/index.js.map +1 -0
  172. package/build-ngc/core/native/exponent-magnetometer-uncalibrated.d.ts +3 -0
  173. package/build-ngc/core/native/exponent-magnetometer-uncalibrated.js +4 -0
  174. package/build-ngc/core/native/exponent-magnetometer-uncalibrated.js.map +1 -0
  175. package/build-ngc/core/native/exponent-magnetometer.d.ts +3 -0
  176. package/build-ngc/core/native/exponent-magnetometer.js +4 -0
  177. package/build-ngc/core/native/exponent-magnetometer.js.map +1 -0
  178. package/build-ngc/core/native/exponent-pedometer.d.ts +10 -0
  179. package/build-ngc/core/native/exponent-pedometer.js +4 -0
  180. package/build-ngc/core/native/exponent-pedometer.js.map +1 -0
  181. package/build-ngc/core/pedometer.d.ts +10 -0
  182. package/build-ngc/core/pedometer.js +45 -0
  183. package/build-ngc/core/pedometer.js.map +1 -0
  184. package/package.json +106 -0
  185. package/src/angular/index.ts +26 -0
  186. package/src/angular/services/accelerometer.service/accelerometer.service.test.ts +118 -0
  187. package/src/angular/services/accelerometer.service/index.ts +39 -0
  188. package/src/angular/services/barometer.service/barometer.service.test.ts +122 -0
  189. package/src/angular/services/barometer.service/index.ts +39 -0
  190. package/src/angular/services/device-motion.service/device-motion.service.test.ts +125 -0
  191. package/src/angular/services/device-motion.service/index.ts +39 -0
  192. package/src/angular/services/gyroscope.service/gyroscope.service.test.ts +118 -0
  193. package/src/angular/services/gyroscope.service/index.ts +39 -0
  194. package/src/angular/services/light-sensor.service/index.ts +39 -0
  195. package/src/angular/services/light-sensor.service/light-sensor.service.test.ts +118 -0
  196. package/src/angular/services/magnetometer-uncalibrated.service/index.ts +39 -0
  197. package/src/angular/services/magnetometer-uncalibrated.service/magnetometer-uncalibrated.service.test.ts +123 -0
  198. package/src/angular/services/magnetometer.service/index.ts +39 -0
  199. package/src/angular/services/magnetometer.service/magnetometer.service.test.ts +118 -0
  200. package/src/angular/services/pedometer.service/index.ts +34 -0
  201. package/src/angular/services/pedometer.service/pedometer.service.test.ts +86 -0
  202. package/src/core/accelerometer.test.ts +49 -0
  203. package/src/core/accelerometer.ts +19 -0
  204. package/src/core/barometer.test.ts +46 -0
  205. package/src/core/barometer.ts +15 -0
  206. package/src/core/device-motion.test.ts +60 -0
  207. package/src/core/device-motion.ts +41 -0
  208. package/src/core/device-sensor.test.ts +76 -0
  209. package/src/core/device-sensor.ts +95 -0
  210. package/src/core/gyroscope.test.ts +46 -0
  211. package/src/core/gyroscope.ts +16 -0
  212. package/src/core/index.ts +37 -0
  213. package/src/core/light-sensor.test.ts +49 -0
  214. package/src/core/light-sensor.ts +18 -0
  215. package/src/core/magnetometer-uncalibrated.test.ts +49 -0
  216. package/src/core/magnetometer-uncalibrated.ts +20 -0
  217. package/src/core/magnetometer.test.ts +49 -0
  218. package/src/core/magnetometer.ts +19 -0
  219. package/src/core/native/exponent-accelerometer.ts +9 -0
  220. package/src/core/native/exponent-barometer.ts +9 -0
  221. package/src/core/native/exponent-device-motion.ts +15 -0
  222. package/src/core/native/exponent-gyroscope.ts +9 -0
  223. package/src/core/native/exponent-light-sensor/index.android.ts +9 -0
  224. package/src/core/native/exponent-light-sensor/index.ios.ts +12 -0
  225. package/src/core/native/exponent-light-sensor/index.ts +5 -0
  226. package/src/core/native/exponent-magnetometer-uncalibrated.ts +9 -0
  227. package/src/core/native/exponent-magnetometer.ts +9 -0
  228. package/src/core/native/exponent-pedometer.ts +17 -0
  229. package/src/core/pedometer.test.ts +118 -0
  230. package/src/core/pedometer.ts +62 -0
  231. package/src/react/hooks/use-accelerometer/index.ts +21 -0
  232. package/src/react/hooks/use-accelerometer/use-accelerometer.test.tsx +81 -0
  233. package/src/react/hooks/use-barometer/index.ts +21 -0
  234. package/src/react/hooks/use-barometer/use-barometer.test.tsx +83 -0
  235. package/src/react/hooks/use-device-motion/index.ts +21 -0
  236. package/src/react/hooks/use-device-motion/use-device-motion.test.tsx +88 -0
  237. package/src/react/hooks/use-gyroscope/index.ts +21 -0
  238. package/src/react/hooks/use-gyroscope/use-gyroscope.test.tsx +79 -0
  239. package/src/react/hooks/use-light-sensor/index.ts +21 -0
  240. package/src/react/hooks/use-light-sensor/use-light-sensor.test.tsx +79 -0
  241. package/src/react/hooks/use-magnetometer/index.ts +21 -0
  242. package/src/react/hooks/use-magnetometer/use-magnetometer.test.tsx +81 -0
  243. package/src/react/hooks/use-magnetometer-uncalibrated/index.ts +23 -0
  244. package/src/react/hooks/use-magnetometer-uncalibrated/use-magnetometer-uncalibrated.test.tsx +88 -0
  245. package/src/react/hooks/use-pedometer/index.ts +17 -0
  246. package/src/react/hooks/use-pedometer/use-pedometer.test.tsx +69 -0
  247. package/src/react/index.ts +29 -0
  248. package/src/vue/composables/use-accelerometer/index.ts +34 -0
  249. package/src/vue/composables/use-accelerometer/use-accelerometer.test.ts +96 -0
  250. package/src/vue/composables/use-barometer/index.ts +30 -0
  251. package/src/vue/composables/use-barometer/use-barometer.test.ts +100 -0
  252. package/src/vue/composables/use-device-motion/index.ts +30 -0
  253. package/src/vue/composables/use-device-motion/use-device-motion.test.ts +104 -0
  254. package/src/vue/composables/use-gyroscope/index.ts +30 -0
  255. package/src/vue/composables/use-gyroscope/use-gyroscope.test.ts +96 -0
  256. package/src/vue/composables/use-light-sensor/index.ts +30 -0
  257. package/src/vue/composables/use-light-sensor/use-light-sensor.test.ts +96 -0
  258. package/src/vue/composables/use-magnetometer/index.ts +30 -0
  259. package/src/vue/composables/use-magnetometer/use-magnetometer.test.ts +96 -0
  260. package/src/vue/composables/use-magnetometer-uncalibrated/index.ts +36 -0
  261. package/src/vue/composables/use-magnetometer-uncalibrated/use-magnetometer-uncalibrated.test.ts +103 -0
  262. package/src/vue/composables/use-pedometer/index.ts +25 -0
  263. package/src/vue/composables/use-pedometer/use-pedometer.test.ts +77 -0
  264. package/src/vue/index.ts +29 -0
@@ -0,0 +1,21 @@
1
+ // React lifecycle wiring over the framework-agnostic LightSensor singleton (core/) —
2
+ // mirrors the lifecycle-bucket naming convention of adapters/react/src/hooks.
3
+ import { useEffect, useState } from 'react';
4
+ import { LightSensor, type ILightSensorMeasurement } from '../../../core';
5
+
6
+ export function useLightSensor(updateIntervalMs?: number): ILightSensorMeasurement | null {
7
+ const [measurement, setMeasurement] = useState<ILightSensorMeasurement | null>(null);
8
+
9
+ useEffect(() => {
10
+ // Re-subscribing on interval change keeps the native update rate in sync with the
11
+ // caller's latest `updateIntervalMs` rather than only ever honoring the first mount's.
12
+ if (updateIntervalMs !== undefined) {
13
+ LightSensor.setUpdateInterval(updateIntervalMs);
14
+ }
15
+
16
+ const subscription = LightSensor.addListener(setMeasurement);
17
+ return () => subscription.remove();
18
+ }, [updateIntervalMs]);
19
+
20
+ return measurement;
21
+ }
@@ -0,0 +1,79 @@
1
+ // Co-located React-driven test (ADR 0025) for useLightSensor. Mocks the whole `core`
2
+ // module rather than expo-modules-core internals — this hook's own lifecycle wiring
3
+ // (subscribe/unsubscribe/interval) is what's under test, not the core port itself, which
4
+ // already has its own coverage in packages/sensors/src/core/*.test.ts.
5
+
6
+ import { createElement, type ReactElement } from 'react';
7
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
8
+ import { mount, unmount, View } from '@symbiote-native/react';
9
+ import { installFabric } from '@symbiote-native/test-utils';
10
+ import { useLightSensor } from './index';
11
+ import type { ILightSensorMeasurement } from '../../../core';
12
+
13
+ const { addListener, removeAllListeners, setUpdateInterval, remove } = vi.hoisted(() => {
14
+ const remove = vi.fn();
15
+ return {
16
+ addListener: vi.fn((_listener: (measurement: ILightSensorMeasurement) => void) => ({ remove })),
17
+ removeAllListeners: vi.fn(),
18
+ setUpdateInterval: vi.fn(),
19
+ remove,
20
+ };
21
+ });
22
+
23
+ vi.mock('../../../core', () => ({
24
+ LightSensor: { addListener, removeAllListeners, setUpdateInterval },
25
+ }));
26
+
27
+ const ROOT_TAG = 902;
28
+
29
+ const results: Array<ILightSensorMeasurement | null> = [];
30
+
31
+ function Probe(props: { updateIntervalMs?: number }): ReactElement {
32
+ results.push(useLightSensor(props.updateIntervalMs));
33
+ return createElement(View);
34
+ }
35
+
36
+ const fabric = installFabric();
37
+
38
+ beforeEach(() => {
39
+ fabric.reset();
40
+ results.length = 0;
41
+ vi.clearAllMocks();
42
+ });
43
+
44
+ afterEach(() => unmount(ROOT_TAG));
45
+
46
+ describe('useLightSensor', () => {
47
+ it('reports null before any native measurement arrives', () => {
48
+ mount(ROOT_TAG, createElement(Probe, {}));
49
+
50
+ expect(results[results.length - 1]).toBeNull();
51
+ });
52
+
53
+ it('updates to the latest measurement once the native listener fires', async () => {
54
+ mount(ROOT_TAG, createElement(Probe, {}));
55
+
56
+ const measurement: ILightSensorMeasurement = { illuminance: 42, timestamp: 123 };
57
+ const listener = addListener.mock.calls[0][0];
58
+ listener(measurement);
59
+
60
+ // The mock invokes the listener directly, outside the engine's event dispatcher
61
+ // (setEventDispatcher in render.ts), which is what normally flushes a native-driven
62
+ // setState synchronously — so the resulting re-render lands on a later microtask here.
63
+ await vi.waitFor(() => expect(results[results.length - 1]).toEqual(measurement));
64
+ });
65
+
66
+ it('unsubscribes from the native listener on unmount', () => {
67
+ mount(ROOT_TAG, createElement(Probe, {}));
68
+
69
+ unmount(ROOT_TAG);
70
+
71
+ expect(remove).toHaveBeenCalledTimes(1);
72
+ });
73
+
74
+ it('sets the native update interval when updateIntervalMs is provided', () => {
75
+ mount(ROOT_TAG, createElement(Probe, { updateIntervalMs: 100 }));
76
+
77
+ expect(setUpdateInterval).toHaveBeenCalledWith(100);
78
+ });
79
+ });
@@ -0,0 +1,21 @@
1
+ // React lifecycle wiring over the framework-agnostic Magnetometer singleton (core/) —
2
+ // mirrors the lifecycle-bucket naming convention of adapters/react/src/hooks.
3
+ import { useEffect, useState } from 'react';
4
+ import { Magnetometer, type IMagnetometerMeasurement } from '../../../core';
5
+
6
+ export function useMagnetometer(updateIntervalMs?: number): IMagnetometerMeasurement | null {
7
+ const [measurement, setMeasurement] = useState<IMagnetometerMeasurement | null>(null);
8
+
9
+ useEffect(() => {
10
+ // Re-subscribing on interval change keeps the native update rate in sync with the
11
+ // caller's latest `updateIntervalMs` rather than only ever honoring the first mount's.
12
+ if (updateIntervalMs !== undefined) {
13
+ Magnetometer.setUpdateInterval(updateIntervalMs);
14
+ }
15
+
16
+ const subscription = Magnetometer.addListener(setMeasurement);
17
+ return () => subscription.remove();
18
+ }, [updateIntervalMs]);
19
+
20
+ return measurement;
21
+ }
@@ -0,0 +1,81 @@
1
+ // Co-located React-driven test (ADR 0025) for useMagnetometer. Mocks the whole `core`
2
+ // module rather than expo-modules-core internals — this hook's own lifecycle wiring
3
+ // (subscribe/unsubscribe/interval) is what's under test, not the core port itself, which
4
+ // already has its own coverage in packages/sensors/src/core/*.test.ts.
5
+
6
+ import { createElement, type ReactElement } from 'react';
7
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
8
+ import { mount, unmount, View } from '@symbiote-native/react';
9
+ import { installFabric } from '@symbiote-native/test-utils';
10
+ import { useMagnetometer } from './index';
11
+ import type { IMagnetometerMeasurement } from '../../../core';
12
+
13
+ const { addListener, removeAllListeners, setUpdateInterval, remove } = vi.hoisted(() => {
14
+ const remove = vi.fn();
15
+ return {
16
+ addListener: vi.fn((_listener: (measurement: IMagnetometerMeasurement) => void) => ({
17
+ remove,
18
+ })),
19
+ removeAllListeners: vi.fn(),
20
+ setUpdateInterval: vi.fn(),
21
+ remove,
22
+ };
23
+ });
24
+
25
+ vi.mock('../../../core', () => ({
26
+ Magnetometer: { addListener, removeAllListeners, setUpdateInterval },
27
+ }));
28
+
29
+ const ROOT_TAG = 902;
30
+
31
+ const results: Array<IMagnetometerMeasurement | null> = [];
32
+
33
+ function Probe(props: { updateIntervalMs?: number }): ReactElement {
34
+ results.push(useMagnetometer(props.updateIntervalMs));
35
+ return createElement(View);
36
+ }
37
+
38
+ const fabric = installFabric();
39
+
40
+ beforeEach(() => {
41
+ fabric.reset();
42
+ results.length = 0;
43
+ vi.clearAllMocks();
44
+ });
45
+
46
+ afterEach(() => unmount(ROOT_TAG));
47
+
48
+ describe('useMagnetometer', () => {
49
+ it('reports null before any native measurement arrives', () => {
50
+ mount(ROOT_TAG, createElement(Probe, {}));
51
+
52
+ expect(results[results.length - 1]).toBeNull();
53
+ });
54
+
55
+ it('updates to the latest measurement once the native listener fires', async () => {
56
+ mount(ROOT_TAG, createElement(Probe, {}));
57
+
58
+ const measurement: IMagnetometerMeasurement = { x: 0.1, y: 0.2, z: 0.3, timestamp: 123 };
59
+ const listener = addListener.mock.calls[0][0];
60
+ listener(measurement);
61
+
62
+ // The mock invokes the listener directly, outside the engine's event dispatcher
63
+ // (setEventDispatcher in render.ts), which is what normally flushes a native-driven
64
+ // setState synchronously — so the resulting re-render lands on a later microtask here.
65
+ await vi.waitFor(() => expect(results[results.length - 1]).toEqual(measurement));
66
+ });
67
+
68
+ it('unsubscribes from the native listener on unmount', () => {
69
+ mount(ROOT_TAG, createElement(Probe, {}));
70
+
71
+ unmount(ROOT_TAG);
72
+
73
+ expect(remove).toHaveBeenCalledTimes(1);
74
+ });
75
+
76
+ it('sets the native update interval when updateIntervalMs is provided', () => {
77
+ mount(ROOT_TAG, createElement(Probe, { updateIntervalMs: 100 }));
78
+
79
+ expect(setUpdateInterval).toHaveBeenCalledWith(100);
80
+ });
81
+ });
@@ -0,0 +1,23 @@
1
+ // React lifecycle wiring over the framework-agnostic MagnetometerUncalibrated singleton
2
+ // (core/) — mirrors the lifecycle-bucket naming convention of adapters/react/src/hooks.
3
+ import { useEffect, useState } from 'react';
4
+ import { MagnetometerUncalibrated, type IMagnetometerUncalibratedMeasurement } from '../../../core';
5
+
6
+ export function useMagnetometerUncalibrated(
7
+ updateIntervalMs?: number,
8
+ ): IMagnetometerUncalibratedMeasurement | null {
9
+ const [measurement, setMeasurement] = useState<IMagnetometerUncalibratedMeasurement | null>(null);
10
+
11
+ useEffect(() => {
12
+ // Re-subscribing on interval change keeps the native update rate in sync with the
13
+ // caller's latest `updateIntervalMs` rather than only ever honoring the first mount's.
14
+ if (updateIntervalMs !== undefined) {
15
+ MagnetometerUncalibrated.setUpdateInterval(updateIntervalMs);
16
+ }
17
+
18
+ const subscription = MagnetometerUncalibrated.addListener(setMeasurement);
19
+ return () => subscription.remove();
20
+ }, [updateIntervalMs]);
21
+
22
+ return measurement;
23
+ }
@@ -0,0 +1,88 @@
1
+ // Co-located React-driven test (ADR 0025) for useMagnetometerUncalibrated. Mocks the whole
2
+ // `core` module rather than expo-modules-core internals — this hook's own lifecycle wiring
3
+ // (subscribe/unsubscribe/interval) is what's under test, not the core port itself, which
4
+ // already has its own coverage in packages/sensors/src/core/*.test.ts.
5
+
6
+ import { createElement, type ReactElement } from 'react';
7
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
8
+ import { mount, unmount, View } from '@symbiote-native/react';
9
+ import { installFabric } from '@symbiote-native/test-utils';
10
+ import { useMagnetometerUncalibrated } from './index';
11
+ import type { IMagnetometerUncalibratedMeasurement } from '../../../core';
12
+
13
+ const { addListener, removeAllListeners, setUpdateInterval, remove } = vi.hoisted(() => {
14
+ const remove = vi.fn();
15
+ return {
16
+ addListener: vi.fn(
17
+ (_listener: (measurement: IMagnetometerUncalibratedMeasurement) => void) => ({
18
+ remove,
19
+ }),
20
+ ),
21
+ removeAllListeners: vi.fn(),
22
+ setUpdateInterval: vi.fn(),
23
+ remove,
24
+ };
25
+ });
26
+
27
+ vi.mock('../../../core', () => ({
28
+ MagnetometerUncalibrated: { addListener, removeAllListeners, setUpdateInterval },
29
+ }));
30
+
31
+ const ROOT_TAG = 902;
32
+
33
+ const results: Array<IMagnetometerUncalibratedMeasurement | null> = [];
34
+
35
+ function Probe(props: { updateIntervalMs?: number }): ReactElement {
36
+ results.push(useMagnetometerUncalibrated(props.updateIntervalMs));
37
+ return createElement(View);
38
+ }
39
+
40
+ const fabric = installFabric();
41
+
42
+ beforeEach(() => {
43
+ fabric.reset();
44
+ results.length = 0;
45
+ vi.clearAllMocks();
46
+ });
47
+
48
+ afterEach(() => unmount(ROOT_TAG));
49
+
50
+ describe('useMagnetometerUncalibrated', () => {
51
+ it('reports null before any native measurement arrives', () => {
52
+ mount(ROOT_TAG, createElement(Probe, {}));
53
+
54
+ expect(results[results.length - 1]).toBeNull();
55
+ });
56
+
57
+ it('updates to the latest measurement once the native listener fires', async () => {
58
+ mount(ROOT_TAG, createElement(Probe, {}));
59
+
60
+ const measurement: IMagnetometerUncalibratedMeasurement = {
61
+ x: 0.1,
62
+ y: 0.2,
63
+ z: 0.3,
64
+ timestamp: 123,
65
+ };
66
+ const listener = addListener.mock.calls[0][0];
67
+ listener(measurement);
68
+
69
+ // The mock invokes the listener directly, outside the engine's event dispatcher
70
+ // (setEventDispatcher in render.ts), which is what normally flushes a native-driven
71
+ // setState synchronously — so the resulting re-render lands on a later microtask here.
72
+ await vi.waitFor(() => expect(results[results.length - 1]).toEqual(measurement));
73
+ });
74
+
75
+ it('unsubscribes from the native listener on unmount', () => {
76
+ mount(ROOT_TAG, createElement(Probe, {}));
77
+
78
+ unmount(ROOT_TAG);
79
+
80
+ expect(remove).toHaveBeenCalledTimes(1);
81
+ });
82
+
83
+ it('sets the native update interval when updateIntervalMs is provided', () => {
84
+ mount(ROOT_TAG, createElement(Probe, { updateIntervalMs: 100 }));
85
+
86
+ expect(setUpdateInterval).toHaveBeenCalledWith(100);
87
+ });
88
+ });
@@ -0,0 +1,17 @@
1
+ // React lifecycle wiring over the framework-agnostic watchStepCount free function (core/) —
2
+ // mirrors the lifecycle-bucket naming convention of adapters/react/src/hooks. Unlike
3
+ // useAccelerometer, Pedometer has no setUpdateInterval, so there is no updateIntervalMs param
4
+ // to sync on re-render.
5
+ import { useEffect, useState } from 'react';
6
+ import { watchStepCount, type IPedometerResult } from '../../../core';
7
+
8
+ export function usePedometer(): IPedometerResult | null {
9
+ const [result, setResult] = useState<IPedometerResult | null>(null);
10
+
11
+ useEffect(() => {
12
+ const subscription = watchStepCount(setResult);
13
+ return () => subscription.remove();
14
+ }, []);
15
+
16
+ return result;
17
+ }
@@ -0,0 +1,69 @@
1
+ // Co-located React-driven test (ADR 0025) for usePedometer. Mocks the whole `core` module
2
+ // rather than expo-modules-core internals — this hook's own lifecycle wiring (subscribe/
3
+ // unsubscribe) is what's under test, not the core port itself, which already has its own
4
+ // coverage in packages/sensors/src/core/pedometer.test.ts.
5
+
6
+ import { createElement, type ReactElement } from 'react';
7
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
8
+ import { mount, unmount, View } from '@symbiote-native/react';
9
+ import { installFabric } from '@symbiote-native/test-utils';
10
+ import { usePedometer } from './index';
11
+ import type { IPedometerResult } from '../../../core';
12
+
13
+ const { watchStepCount, remove } = vi.hoisted(() => {
14
+ const remove = vi.fn();
15
+ return {
16
+ watchStepCount: vi.fn((_callback: (result: IPedometerResult) => void) => ({ remove })),
17
+ remove,
18
+ };
19
+ });
20
+
21
+ vi.mock('../../../core', () => ({ watchStepCount }));
22
+
23
+ const ROOT_TAG = 902;
24
+
25
+ const results: Array<IPedometerResult | null> = [];
26
+
27
+ function Probe(): ReactElement {
28
+ results.push(usePedometer());
29
+ return createElement(View);
30
+ }
31
+
32
+ const fabric = installFabric();
33
+
34
+ beforeEach(() => {
35
+ fabric.reset();
36
+ results.length = 0;
37
+ vi.clearAllMocks();
38
+ });
39
+
40
+ afterEach(() => unmount(ROOT_TAG));
41
+
42
+ describe('usePedometer', () => {
43
+ it('reports null before any native step count arrives', () => {
44
+ mount(ROOT_TAG, createElement(Probe));
45
+
46
+ expect(results[results.length - 1]).toBeNull();
47
+ });
48
+
49
+ it('updates to the latest result once the native listener fires', async () => {
50
+ mount(ROOT_TAG, createElement(Probe));
51
+
52
+ const result: IPedometerResult = { steps: 123 };
53
+ const listener = watchStepCount.mock.calls[0][0];
54
+ listener(result);
55
+
56
+ // The mock invokes the listener directly, outside the engine's event dispatcher
57
+ // (setEventDispatcher in render.ts), which is what normally flushes a native-driven
58
+ // setState synchronously — so the resulting re-render lands on a later microtask here.
59
+ await vi.waitFor(() => expect(results[results.length - 1]).toEqual(result));
60
+ });
61
+
62
+ it('unsubscribes from the native listener on unmount', () => {
63
+ mount(ROOT_TAG, createElement(Probe));
64
+
65
+ unmount(ROOT_TAG);
66
+
67
+ expect(remove).toHaveBeenCalledTimes(1);
68
+ });
69
+ });
@@ -0,0 +1,29 @@
1
+ // @symbiote-native/sensors/react: the React entry over the framework-agnostic core.
2
+ // useAccelerometer wraps the Accelerometer singleton with React's own lifecycle
3
+ // (hooks/use-accelerometer) — mirrors the lifecycle-bucket naming convention of
4
+ // adapters/react/src/hooks (never `composables`, that's Vue's term).
5
+
6
+ export { useAccelerometer } from './hooks/use-accelerometer';
7
+ export type { IAccelerometerMeasurement } from '../core';
8
+ export { useBarometer } from './hooks/use-barometer';
9
+ export type { IBarometerMeasurement } from '../core';
10
+ export { useDeviceMotion } from './hooks/use-device-motion';
11
+ export { DeviceMotionOrientation, gravity, type IDeviceMotionMeasurement } from '../core';
12
+ export { useGyroscope } from './hooks/use-gyroscope';
13
+ export type { IGyroscopeMeasurement } from '../core';
14
+ export { useLightSensor } from './hooks/use-light-sensor';
15
+ export type { ILightSensorMeasurement } from '../core';
16
+ export { useMagnetometer } from './hooks/use-magnetometer';
17
+ export type { IMagnetometerMeasurement } from '../core';
18
+ export { useMagnetometerUncalibrated } from './hooks/use-magnetometer-uncalibrated';
19
+ export type { IMagnetometerUncalibratedMeasurement } from '../core';
20
+ export { usePedometer } from './hooks/use-pedometer';
21
+ export {
22
+ watchStepCount,
23
+ getStepCountAsync,
24
+ isAvailableAsync,
25
+ getPermissionsAsync,
26
+ requestPermissionsAsync,
27
+ type IPedometerResult,
28
+ type IPedometerUpdateCallback,
29
+ } from '../core';
@@ -0,0 +1,34 @@
1
+ // Vue lifecycle wiring over the framework-agnostic Accelerometer singleton (core/) — mirrors
2
+ // the lifecycle-bucket naming convention of adapters/vue/src/composables and the
3
+ // onMounted/onUnmounted shape of use-color-scheme.ts's subscription handle. Unlike
4
+ // use-hide-animation.ts (which takes a config getter because its consumer's config changes
5
+ // across reactive updates), this subscribes exactly once at mount and never needs to react to
6
+ // a later change of updateIntervalMs, so a plain numeric param is enough.
7
+
8
+ import { onMounted, onUnmounted, ref, type Ref } from '@vue/runtime-core';
9
+ import {
10
+ Accelerometer,
11
+ type EventSubscription,
12
+ type IAccelerometerMeasurement,
13
+ } from '../../../core';
14
+
15
+ export function useAccelerometer(updateIntervalMs?: number): Ref<IAccelerometerMeasurement | null> {
16
+ // A plain ref: the value is a POJO measurement, not an engine node, so no shallowRef needed.
17
+ const measurement = ref<IAccelerometerMeasurement | null>(null);
18
+ let subscription: EventSubscription | undefined;
19
+
20
+ onMounted(() => {
21
+ if (updateIntervalMs !== undefined) {
22
+ Accelerometer.setUpdateInterval(updateIntervalMs);
23
+ }
24
+ subscription = Accelerometer.addListener(next => {
25
+ measurement.value = next;
26
+ });
27
+ });
28
+
29
+ onUnmounted(() => {
30
+ subscription?.remove();
31
+ });
32
+
33
+ return measurement;
34
+ }
@@ -0,0 +1,96 @@
1
+ // Co-located Vue-driven test (ADR 0025) for useAccelerometer. Mocks the whole core module
2
+ // (never expo-modules-core internals) since this exercises composable mount/unmount lifecycle
3
+ // timing, not any native view — there is none here, so no ViewConfig fixture is needed.
4
+
5
+ import { defineComponent, h, type Ref } from '@vue/runtime-core';
6
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
7
+ import { mount, unmount } from '@symbiote-native/vue';
8
+ import { installFabric } from '@symbiote-native/test-utils';
9
+ import type { IAccelerometerMeasurement } from '../../../core';
10
+ import { useAccelerometer } from './index';
11
+
12
+ const ROOT_TAG = 9821;
13
+
14
+ type IListener = (measurement: IAccelerometerMeasurement) => void;
15
+
16
+ let registeredListener: IListener | undefined;
17
+ const removeMock = vi.fn();
18
+ const addListenerMock = vi.fn((listener: IListener) => {
19
+ registeredListener = listener;
20
+ return { remove: removeMock };
21
+ });
22
+ const setUpdateIntervalMock = vi.fn();
23
+
24
+ vi.mock('../../../core', () => ({
25
+ Accelerometer: {
26
+ addListener: (listener: IListener) => addListenerMock(listener),
27
+ removeAllListeners: vi.fn(),
28
+ setUpdateInterval: (intervalMs: number) => setUpdateIntervalMock(intervalMs),
29
+ },
30
+ }));
31
+
32
+ const fabric = installFabric();
33
+
34
+ beforeEach(() => {
35
+ fabric.reset();
36
+ registeredListener = undefined;
37
+ addListenerMock.mockClear();
38
+ removeMock.mockClear();
39
+ setUpdateIntervalMock.mockClear();
40
+ });
41
+
42
+ afterEach(() => unmount(ROOT_TAG));
43
+
44
+ function mountAccelerometer(updateIntervalMs?: number): Ref<IAccelerometerMeasurement | null> {
45
+ let measurement: Ref<IAccelerometerMeasurement | null> | undefined;
46
+ mount(
47
+ ROOT_TAG,
48
+ defineComponent({
49
+ setup: () => {
50
+ measurement = useAccelerometer(updateIntervalMs);
51
+ return () => h('symbiote-text', {}, 'sensor');
52
+ },
53
+ }),
54
+ );
55
+ if (measurement === undefined) {
56
+ throw new Error('setup() did not run');
57
+ }
58
+ return measurement;
59
+ }
60
+
61
+ describe('useAccelerometer (Vue)', () => {
62
+ it('starts null before any measurement arrives', () => {
63
+ const measurement = mountAccelerometer();
64
+
65
+ expect(measurement.value).toBeNull();
66
+ });
67
+
68
+ it('updates the ref when the native listener fires', () => {
69
+ const measurement = mountAccelerometer();
70
+ const reading: IAccelerometerMeasurement = { x: 0.1, y: 0.2, z: 0.9, timestamp: 123 };
71
+
72
+ registeredListener?.(reading);
73
+
74
+ expect(measurement.value).toEqual(reading);
75
+ });
76
+
77
+ it('removes the subscription on unmount', () => {
78
+ mountAccelerometer();
79
+ unmount(ROOT_TAG);
80
+
81
+ expect(removeMock).toHaveBeenCalledTimes(1);
82
+ });
83
+
84
+ it('sets the update interval once at subscribe time when provided', () => {
85
+ mountAccelerometer(50);
86
+
87
+ expect(setUpdateIntervalMock).toHaveBeenCalledWith(50);
88
+ expect(setUpdateIntervalMock).toHaveBeenCalledTimes(1);
89
+ });
90
+
91
+ it('does not touch the update interval when omitted', () => {
92
+ mountAccelerometer();
93
+
94
+ expect(setUpdateIntervalMock).not.toHaveBeenCalled();
95
+ });
96
+ });
@@ -0,0 +1,30 @@
1
+ // Vue lifecycle wiring over the framework-agnostic Barometer singleton (core/) — mirrors
2
+ // the lifecycle-bucket naming convention of adapters/vue/src/composables and the
3
+ // onMounted/onUnmounted shape of use-color-scheme.ts's subscription handle. Unlike
4
+ // use-hide-animation.ts (which takes a config getter because its consumer's config changes
5
+ // across reactive updates), this subscribes exactly once at mount and never needs to react to
6
+ // a later change of updateIntervalMs, so a plain numeric param is enough.
7
+
8
+ import { onMounted, onUnmounted, ref, type Ref } from '@vue/runtime-core';
9
+ import { Barometer, type EventSubscription, type IBarometerMeasurement } from '../../../core';
10
+
11
+ export function useBarometer(updateIntervalMs?: number): Ref<IBarometerMeasurement | null> {
12
+ // A plain ref: the value is a POJO measurement, not an engine node, so no shallowRef needed.
13
+ const measurement = ref<IBarometerMeasurement | null>(null);
14
+ let subscription: EventSubscription | undefined;
15
+
16
+ onMounted(() => {
17
+ if (updateIntervalMs !== undefined) {
18
+ Barometer.setUpdateInterval(updateIntervalMs);
19
+ }
20
+ subscription = Barometer.addListener(next => {
21
+ measurement.value = next;
22
+ });
23
+ });
24
+
25
+ onUnmounted(() => {
26
+ subscription?.remove();
27
+ });
28
+
29
+ return measurement;
30
+ }