@symbiote-native/brightness 0.1.0 → 0.3.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 (45) hide show
  1. package/README.md +57 -25
  2. package/build/angular/index.d.ts +1 -1
  3. package/build/angular/index.js +1 -1
  4. package/build/angular/services/permissions.service/index.d.ts +4 -0
  5. package/build/angular/services/permissions.service/index.js +19 -2
  6. package/build/core/brightness.js +1 -1
  7. package/build/react/hooks/use-permissions/index.d.ts +2 -1
  8. package/build/react/hooks/use-permissions/index.js +17 -4
  9. package/build/react/index.d.ts +1 -1
  10. package/build/react/index.js +1 -1
  11. package/build/solid/index.d.ts +2 -0
  12. package/build/solid/index.js +7 -0
  13. package/build/solid/primitives/create-permissions.d.ts +8 -0
  14. package/build/solid/primitives/create-permissions.js +55 -0
  15. package/build/svelte/index.d.ts +2 -0
  16. package/build/svelte/index.js +6 -0
  17. package/build/svelte/runes/use-permissions.svelte.d.ts +7 -0
  18. package/build/svelte/runes/use-permissions.svelte.js +55 -0
  19. package/build/vue/composables/use-permissions/index.d.ts +1 -0
  20. package/build/vue/composables/use-permissions/index.js +12 -2
  21. package/build/vue/index.d.ts +1 -1
  22. package/build/vue/index.js +1 -1
  23. package/build-ngc/angular/index.d.ts +1 -1
  24. package/build-ngc/angular/index.js +1 -1
  25. package/build-ngc/angular/index.js.map +1 -1
  26. package/build-ngc/angular/services/permissions.service/index.d.ts +4 -0
  27. package/build-ngc/angular/services/permissions.service/index.js +23 -5
  28. package/build-ngc/angular/services/permissions.service/index.js.map +1 -1
  29. package/build-ngc/core/brightness.js +1 -1
  30. package/build-ngc/core/brightness.js.map +1 -1
  31. package/build-ngc/core/native-module.js.map +1 -1
  32. package/package.json +42 -14
  33. package/src/angular/index.ts +1 -20
  34. package/src/angular/services/permissions.service/index.ts +22 -2
  35. package/src/core/brightness.ts +41 -11
  36. package/src/core/native-module.ts +5 -1
  37. package/src/react/hooks/use-permissions/index.ts +20 -4
  38. package/src/react/index.ts +1 -20
  39. package/src/solid/index.ts +7 -0
  40. package/src/solid/primitives/create-permissions.ts +69 -0
  41. package/src/svelte/index.ts +7 -0
  42. package/src/svelte/runes/use-permissions.svelte.ts +69 -0
  43. package/src/svelte/svelte-ambient.d.ts +6 -0
  44. package/src/vue/composables/use-permissions/index.ts +13 -2
  45. package/src/vue/index.ts +1 -20
@@ -5,32 +5,50 @@
5
5
  //
6
6
  // readonly status = inject(PermissionsService).connect();
7
7
  // // template: {{ status()?.granted }}
8
+ //
9
+ // The auto-fetch has nobody to reject to, so its failure lands in the `error` signal instead of
10
+ // escaping as an unhandled rejection: a null status with a non-null error is how a consumer tells
11
+ // "the fetch failed" from "not fetched yet". get()/request() still reject to their direct caller —
12
+ // only the automatic call is made safe.
8
13
  import { Injectable, signal } from '@angular/core';
9
14
  import { getPermissionsAsync, requestPermissionsAsync, } from '../../../core';
10
15
  import * as i0 from "@angular/core";
11
16
  export class PermissionsService {
12
17
  status = signal(null, /* @ts-ignore */
13
18
  ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
19
+ fetchError = signal(null, /* @ts-ignore */
20
+ ...(ngDevMode ? [{ debugName: "fetchError" }] : /* istanbul ignore next */ []));
21
+ isAutoFetchStarted = false;
22
+ /** Why the auto-fetch left `status` at null. Cleared by the next successful get()/request(). */
23
+ error = this.fetchError.asReadonly();
14
24
  connect() {
15
- if (this.status() === null) {
16
- void this.get();
25
+ // Latched rather than guarded on `status() === null`: a failed fetch leaves status null
26
+ // forever, so the old guard turned every later connect() into another native call — unbounded
27
+ // once connect() is reached from a change-detected expression instead of a field initializer.
28
+ if (!this.isAutoFetchStarted) {
29
+ this.isAutoFetchStarted = true;
30
+ this.get().catch((cause) => {
31
+ this.fetchError.set(cause instanceof Error ? cause : new Error(String(cause)));
32
+ });
17
33
  }
18
34
  return this.status.asReadonly();
19
35
  }
20
36
  async get() {
21
37
  const response = await getPermissionsAsync();
22
38
  this.status.set(response);
39
+ this.fetchError.set(null);
23
40
  return response;
24
41
  }
25
42
  async request() {
26
43
  const response = await requestPermissionsAsync();
27
44
  this.status.set(response);
45
+ this.fetchError.set(null);
28
46
  return response;
29
47
  }
30
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: PermissionsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
31
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: PermissionsService, providedIn: 'root' });
48
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PermissionsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
49
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PermissionsService, providedIn: 'root' });
32
50
  }
33
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: PermissionsService, decorators: [{
51
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PermissionsService, decorators: [{
34
52
  type: Injectable,
35
53
  args: [{ providedIn: 'root' }]
36
54
  }] });
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/angular/services/permissions.service/index.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,4FAA4F;AAC5F,yFAAyF;AACzF,+BAA+B;AAC/B,EAAE;AACF,4DAA4D;AAC5D,yCAAyC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAe,MAAM,eAAe,CAAC;AAChE,OAAO,EACL,mBAAmB,EACnB,uBAAuB,GAExB,MAAM,eAAe,CAAC;;AAGvB,MAAM,OAAO,kBAAkB;IACZ,MAAM,GAAG,MAAM,CAA4B,IAAI;+EAAC,CAAC;IAElE,OAAO;QACL,IAAI,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC;YAC3B,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC;QAClB,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,GAAG;QACP,MAAM,QAAQ,GAAG,MAAM,mBAAmB,EAAE,CAAC;QAC7C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC1B,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,QAAQ,GAAG,MAAM,uBAAuB,EAAE,CAAC;QACjD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC1B,OAAO,QAAQ,CAAC;IAClB,CAAC;uGApBU,kBAAkB;2GAAlB,kBAAkB,cADL,MAAM;;2FACnB,kBAAkB;kBAD9B,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/angular/services/permissions.service/index.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,4FAA4F;AAC5F,yFAAyF;AACzF,+BAA+B;AAC/B,EAAE;AACF,4DAA4D;AAC5D,yCAAyC;AACzC,EAAE;AACF,gGAAgG;AAChG,kGAAkG;AAClG,mGAAmG;AACnG,wCAAwC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAe,MAAM,eAAe,CAAC;AAChE,OAAO,EACL,mBAAmB,EACnB,uBAAuB,GAExB,MAAM,eAAe,CAAC;;AAGvB,MAAM,OAAO,kBAAkB;IACZ,MAAM,GAAG,MAAM,CAA4B,IAAI;+EAAC,CAAC;IACjD,UAAU,GAAG,MAAM,CAAe,IAAI;mFAAC,CAAC;IACjD,kBAAkB,GAAG,KAAK,CAAC;IAEnC,gGAAgG;IACvF,KAAK,GAAyB,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;IAEpE,OAAO;QACL,wFAAwF;QACxF,8FAA8F;QAC9F,8FAA8F;QAC9F,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC7B,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;YAC/B,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;gBAClC,IAAI,CAAC,UAAU,CAAC,GAAG,CACjB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAC1D,CAAC;YACJ,CAAC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,GAAG;QACP,MAAM,QAAQ,GAAG,MAAM,mBAAmB,EAAE,CAAC;QAC7C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC1B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC1B,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,QAAQ,GAAG,MAAM,uBAAuB,EAAE,CAAC;QACjD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC1B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC1B,OAAO,QAAQ,CAAC;IAClB,CAAC;uGAnCU,kBAAkB;2GAAlB,kBAAkB,cADL,MAAM;;2FACnB,kBAAkB;kBAD9B,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE"}
@@ -4,7 +4,7 @@
4
4
  // native module, so those branches are preserved verbatim rather than guessed.
5
5
  import { Platform, UnavailabilityError } from 'expo-modules-core';
6
6
  import { expoBrightness } from './native-module';
7
- import { BrightnessMode } from './types';
7
+ import { BrightnessMode, } from './types';
8
8
  const NATIVE_MODULE_NAME = 'expo-brightness';
9
9
  function clampBrightness(brightnessValue, callerName) {
10
10
  const clamped = Math.max(0, Math.min(brightnessValue, 1));
@@ -1 +1 @@
1
- {"version":3,"file":"brightness.js","sourceRoot":"","sources":["../../src/core/brightness.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"brightness.js","sourceRoot":"","sources":["../../src/core/brightness.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,yFAAyF;AACzF,+FAA+F;AAC/F,+EAA+E;AAC/E,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGlE,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EACL,cAAc,GAGf,MAAM,SAAS,CAAC;AAEjB,MAAM,kBAAkB,GAAG,iBAAiB,CAAC;AAE7C,SAAS,eAAe,CAAC,eAAuB,EAAE,UAAkB;IAClE,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,CAAC;IAC1D,IAAI,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,SAAS,CACjB,GAAG,UAAU,0BAA0B,eAAe,EAAE,CACzD,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,qEAAqE;AACrE,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,OAAO,CAAC,CAAC,cAAc,CAAC,kBAAkB,CAAC;AAC7C,CAAC;AAED,0FAA0F;AAC1F,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,IAAI,CAAC,cAAc,CAAC,kBAAkB,EAAE,CAAC;QACvC,MAAM,IAAI,mBAAmB,CAAC,kBAAkB,EAAE,oBAAoB,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,cAAc,CAAC,kBAAkB,EAAE,CAAC;AAC7C,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,eAAuB;IAEvB,MAAM,OAAO,GAAG,eAAe,CAAC,eAAe,EAAE,oBAAoB,CAAC,CAAC;IACvE,IAAI,CAAC,cAAc,CAAC,kBAAkB,EAAE,CAAC;QACvC,MAAM,IAAI,mBAAmB,CAAC,kBAAkB,EAAE,oBAAoB,CAAC,CAAC;IAC1E,CAAC;IACD,MAAM,cAAc,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;AACnD,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB;IAC5C,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,kBAAkB,EAAE,CAAC;IAC9B,CAAC;IACD,IAAI,CAAC,cAAc,CAAC,wBAAwB,EAAE,CAAC;QAC7C,MAAM,IAAI,mBAAmB,CAC3B,kBAAkB,EAClB,0BAA0B,CAC3B,CAAC;IACJ,CAAC;IACD,OAAO,cAAc,CAAC,wBAAwB,EAAE,CAAC;AACnD,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,eAAuB;IAEvB,MAAM,OAAO,GAAG,eAAe,CAAC,eAAe,EAAE,0BAA0B,CAAC,CAAC;IAC7E,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,kBAAkB,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;IACD,IAAI,CAAC,cAAc,CAAC,wBAAwB,EAAE,CAAC;QAC7C,MAAM,IAAI,mBAAmB,CAC3B,kBAAkB,EAClB,0BAA0B,CAC3B,CAAC;IACJ,CAAC;IACD,MAAM,cAAc,CAAC,wBAAwB,CAAC,OAAO,CAAC,CAAC;AACzD,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B;IAChD,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO;IACT,CAAC;IACD,IAAI,CAAC,cAAc,CAAC,4BAA4B,EAAE,CAAC;QACjD,MAAM,IAAI,mBAAmB,CAC3B,kBAAkB,EAClB,8BAA8B,CAC/B,CAAC;IACJ,CAAC;IACD,MAAM,cAAc,CAAC,4BAA4B,EAAE,CAAC;AACtD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B;IAChD,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,CAAC,cAAc,CAAC,4BAA4B,EAAE,CAAC;QACjD,MAAM,IAAI,mBAAmB,CAC3B,kBAAkB,EAClB,8BAA8B,CAC/B,CAAC;IACJ,CAAC;IACD,OAAO,cAAc,CAAC,4BAA4B,EAAE,CAAC;AACvD,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B;IAChD,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,cAAc,CAAC,OAAO,CAAC;IAChC,CAAC;IACD,IAAI,CAAC,cAAc,CAAC,4BAA4B,EAAE,CAAC;QACjD,MAAM,IAAI,mBAAmB,CAC3B,kBAAkB,EAClB,8BAA8B,CAC/B,CAAC;IACJ,CAAC;IACD,OAAO,cAAc,CAAC,4BAA4B,EAAE,CAAC;AACvD,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,cAA8B;IAE9B,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS,IAAI,cAAc,KAAK,cAAc,CAAC,OAAO,EAAE,CAAC;QAC3E,OAAO;IACT,CAAC;IACD,IAAI,CAAC,cAAc,CAAC,4BAA4B,EAAE,CAAC;QACjD,MAAM,IAAI,mBAAmB,CAC3B,kBAAkB,EAClB,8BAA8B,CAC/B,CAAC;IACJ,CAAC;IACD,MAAM,cAAc,CAAC,4BAA4B,CAAC,cAAc,CAAC,CAAC;AACpE,CAAC;AAED,qEAAqE;AACrE,MAAM,CAAC,KAAK,UAAU,mBAAmB;IACvC,OAAO,cAAc,CAAC,mBAAmB,EAAE,CAAC;AAC9C,CAAC;AAED,oEAAoE;AACpE,MAAM,CAAC,KAAK,UAAU,uBAAuB;IAC3C,OAAO,cAAc,CAAC,uBAAuB,EAAE,CAAC;AAClD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CACnC,QAA0C;IAE1C,OAAO,cAAc,CAAC,WAAW,CAAC,0BAA0B,EAAE,QAAQ,CAAC,CAAC;AAC1E,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"native-module.js","sourceRoot":"","sources":["../../src/core/native-module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAIxD,MAAM,2BAA2B,GAAG,gBAAgB,CAAC;AAyBrD,MAAM,CAAC,MAAM,cAAc,GAAG,mBAAmB,CAC/C,2BAA2B,CAC5B,CAAC"}
1
+ {"version":3,"file":"native-module.js","sourceRoot":"","sources":["../../src/core/native-module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAQxD,MAAM,2BAA2B,GAAG,gBAAgB,CAAC;AAyBrD,MAAM,CAAC,MAAM,cAAc,GAAG,mBAAmB,CAC/C,2BAA2B,CAC5B,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@symbiote-native/brightness",
3
- "version": "0.1.0",
4
- "description": "expo-brightness wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, and Angular adapters. Screen brightness get/set, Android-only system-brightness-mode control, an iOS-only brightness-change listener, and permission get/request.",
3
+ "version": "0.3.0",
4
+ "description": "expo-brightness wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, Svelte, Solid, and Angular adapters. Screen brightness get/set, Android-only system-brightness-mode control, an iOS-only brightness-change listener, and permission get/request.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -30,6 +30,14 @@
30
30
  "types": "./build/react/index.d.ts",
31
31
  "default": "./build/react/index.js"
32
32
  },
33
+ "./svelte": {
34
+ "types": "./build/svelte/index.d.ts",
35
+ "default": "./build/svelte/index.js"
36
+ },
37
+ "./solid": {
38
+ "types": "./build/solid/index.d.ts",
39
+ "default": "./build/solid/index.js"
40
+ },
33
41
  "./angular": {
34
42
  "types": "./build-ngc/angular/index.d.ts",
35
43
  "react-native": "./build-ngc/angular/index.js",
@@ -53,15 +61,19 @@
53
61
  "expo-modules-core": "57.0.5"
54
62
  },
55
63
  "peerDependencies": {
64
+ "@symbiote-native/angular": ">=0.6.2",
56
65
  "@symbiote-native/engine": ">=0.1.7",
66
+ "@symbiote-native/react": ">=0.2.8",
67
+ "@symbiote-native/solid": ">=0.0.1",
68
+ "@symbiote-native/svelte": ">=0.1.0",
69
+ "@symbiote-native/vue": ">=0.3.8",
57
70
  "@angular/core": ">=20",
58
71
  "@vue/runtime-core": "^3.5.13",
59
72
  "react": ">=19.0.0",
60
73
  "react-native": ">=0.86",
61
- "vue": ">=3.5.0",
62
- "@symbiote-native/angular": "0.6.2",
63
- "@symbiote-native/react": "0.2.8",
64
- "@symbiote-native/vue": "0.3.8"
74
+ "solid-js": ">=1.9.0",
75
+ "svelte": ">=5.56.0",
76
+ "vue": ">=3.5.0"
65
77
  },
66
78
  "peerDependenciesMeta": {
67
79
  "@symbiote-native/angular": {
@@ -70,6 +82,12 @@
70
82
  "@symbiote-native/react": {
71
83
  "optional": true
72
84
  },
85
+ "@symbiote-native/solid": {
86
+ "optional": true
87
+ },
88
+ "@symbiote-native/svelte": {
89
+ "optional": true
90
+ },
73
91
  "@symbiote-native/vue": {
74
92
  "optional": true
75
93
  },
@@ -82,24 +100,34 @@
82
100
  "react": {
83
101
  "optional": true
84
102
  },
103
+ "solid-js": {
104
+ "optional": true
105
+ },
106
+ "svelte": {
107
+ "optional": true
108
+ },
85
109
  "vue": {
86
110
  "optional": true
87
111
  }
88
112
  },
89
113
  "devDependencies": {
90
- "@angular/compiler": "^22",
91
- "@angular/compiler-cli": "^22",
92
- "@angular/core": "^22",
114
+ "@angular/compiler": "~22.0.8",
115
+ "@angular/compiler-cli": "~22.0.8",
116
+ "@angular/core": "~22.0.8",
93
117
  "@types/node": "^26.0.0",
94
118
  "@types/react": "^19.2.0",
95
119
  "@vue/runtime-core": "^3.5.13",
96
120
  "react": "19.2.3",
121
+ "solid-js": "^1.9.14",
122
+ "svelte": "^5.56.0",
97
123
  "typescript": "~6.0.0",
98
- "@symbiote-native/angular": "0.6.2",
99
- "@symbiote-native/engine": "0.1.7",
100
- "@symbiote-native/react": "0.2.8",
101
- "@symbiote-native/test-utils": "0.1.6",
102
- "@symbiote-native/vue": "0.3.8"
124
+ "@symbiote-native/angular": "0.8.0",
125
+ "@symbiote-native/engine": "0.3.0",
126
+ "@symbiote-native/react": "0.4.0",
127
+ "@symbiote-native/solid": "0.1.0",
128
+ "@symbiote-native/svelte": "0.3.0",
129
+ "@symbiote-native/test-utils": "0.2.0",
130
+ "@symbiote-native/vue": "0.5.0"
103
131
  },
104
132
  "scripts": {
105
133
  "typecheck": "tsc --build",
@@ -1,23 +1,4 @@
1
1
  // PermissionsService is the Angular-only lifecycle half; the free functions and event
2
2
  // subscription plumbing all live in core, shared with React/Vue.
3
3
  export { PermissionsService } from './services/permissions.service';
4
- export {
5
- isAvailableAsync,
6
- getBrightnessAsync,
7
- setBrightnessAsync,
8
- getSystemBrightnessAsync,
9
- setSystemBrightnessAsync,
10
- restoreSystemBrightnessAsync,
11
- isUsingSystemBrightnessAsync,
12
- getSystemBrightnessModeAsync,
13
- setSystemBrightnessModeAsync,
14
- getPermissionsAsync,
15
- requestPermissionsAsync,
16
- addBrightnessListener,
17
- BrightnessMode,
18
- PermissionStatus,
19
- type BrightnessEvent,
20
- type PermissionExpiration,
21
- type PermissionResponse,
22
- type EventSubscription,
23
- } from '../core';
4
+ export * from '../core';
@@ -5,6 +5,11 @@
5
5
  //
6
6
  // readonly status = inject(PermissionsService).connect();
7
7
  // // template: {{ status()?.granted }}
8
+ //
9
+ // The auto-fetch has nobody to reject to, so its failure lands in the `error` signal instead of
10
+ // escaping as an unhandled rejection: a null status with a non-null error is how a consumer tells
11
+ // "the fetch failed" from "not fetched yet". get()/request() still reject to their direct caller —
12
+ // only the automatic call is made safe.
8
13
  import { Injectable, signal, type Signal } from '@angular/core';
9
14
  import {
10
15
  getPermissionsAsync,
@@ -15,10 +20,23 @@ import {
15
20
  @Injectable({ providedIn: 'root' })
16
21
  export class PermissionsService {
17
22
  private readonly status = signal<PermissionResponse | null>(null);
23
+ private readonly fetchError = signal<Error | null>(null);
24
+ private isAutoFetchStarted = false;
25
+
26
+ /** Why the auto-fetch left `status` at null. Cleared by the next successful get()/request(). */
27
+ readonly error: Signal<Error | null> = this.fetchError.asReadonly();
18
28
 
19
29
  connect(): Signal<PermissionResponse | null> {
20
- if (this.status() === null) {
21
- void this.get();
30
+ // Latched rather than guarded on `status() === null`: a failed fetch leaves status null
31
+ // forever, so the old guard turned every later connect() into another native call — unbounded
32
+ // once connect() is reached from a change-detected expression instead of a field initializer.
33
+ if (!this.isAutoFetchStarted) {
34
+ this.isAutoFetchStarted = true;
35
+ this.get().catch((cause: unknown) => {
36
+ this.fetchError.set(
37
+ cause instanceof Error ? cause : new Error(String(cause)),
38
+ );
39
+ });
22
40
  }
23
41
  return this.status.asReadonly();
24
42
  }
@@ -26,12 +44,14 @@ export class PermissionsService {
26
44
  async get(): Promise<PermissionResponse> {
27
45
  const response = await getPermissionsAsync();
28
46
  this.status.set(response);
47
+ this.fetchError.set(null);
29
48
  return response;
30
49
  }
31
50
 
32
51
  async request(): Promise<PermissionResponse> {
33
52
  const response = await requestPermissionsAsync();
34
53
  this.status.set(response);
54
+ this.fetchError.set(null);
35
55
  return response;
36
56
  }
37
57
  }
@@ -6,14 +6,20 @@ import { Platform, UnavailabilityError } from 'expo-modules-core';
6
6
  import type { EventSubscription } from 'expo-modules-core';
7
7
 
8
8
  import { expoBrightness } from './native-module';
9
- import { BrightnessMode, type BrightnessEvent, type PermissionResponse } from './types';
9
+ import {
10
+ BrightnessMode,
11
+ type BrightnessEvent,
12
+ type PermissionResponse,
13
+ } from './types';
10
14
 
11
15
  const NATIVE_MODULE_NAME = 'expo-brightness';
12
16
 
13
17
  function clampBrightness(brightnessValue: number, callerName: string): number {
14
18
  const clamped = Math.max(0, Math.min(brightnessValue, 1));
15
19
  if (Number.isNaN(clamped)) {
16
- throw new TypeError(`${callerName} cannot be called with ${brightnessValue}`);
20
+ throw new TypeError(
21
+ `${callerName} cannot be called with ${brightnessValue}`,
22
+ );
17
23
  }
18
24
  return clamped;
19
25
  }
@@ -35,7 +41,9 @@ export async function getBrightnessAsync(): Promise<number> {
35
41
  * Sets the current screen brightness. On iOS this only affects the screen while the app is
36
42
  * foregrounded; on Android it persists until changed again.
37
43
  */
38
- export async function setBrightnessAsync(brightnessValue: number): Promise<void> {
44
+ export async function setBrightnessAsync(
45
+ brightnessValue: number,
46
+ ): Promise<void> {
39
47
  const clamped = clampBrightness(brightnessValue, 'setBrightnessAsync');
40
48
  if (!expoBrightness.setBrightnessAsync) {
41
49
  throw new UnavailabilityError(NATIVE_MODULE_NAME, 'setBrightnessAsync');
@@ -53,7 +61,10 @@ export async function getSystemBrightnessAsync(): Promise<number> {
53
61
  return getBrightnessAsync();
54
62
  }
55
63
  if (!expoBrightness.getSystemBrightnessAsync) {
56
- throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getSystemBrightnessAsync');
64
+ throw new UnavailabilityError(
65
+ NATIVE_MODULE_NAME,
66
+ 'getSystemBrightnessAsync',
67
+ );
57
68
  }
58
69
  return expoBrightness.getSystemBrightnessAsync();
59
70
  }
@@ -63,13 +74,18 @@ export async function getSystemBrightnessAsync(): Promise<number> {
63
74
  * no separate system-level brightness to set.
64
75
  * @platform android
65
76
  */
66
- export async function setSystemBrightnessAsync(brightnessValue: number): Promise<void> {
77
+ export async function setSystemBrightnessAsync(
78
+ brightnessValue: number,
79
+ ): Promise<void> {
67
80
  const clamped = clampBrightness(brightnessValue, 'setSystemBrightnessAsync');
68
81
  if (Platform.OS !== 'android') {
69
82
  return setBrightnessAsync(clamped);
70
83
  }
71
84
  if (!expoBrightness.setSystemBrightnessAsync) {
72
- throw new UnavailabilityError(NATIVE_MODULE_NAME, 'setSystemBrightnessAsync');
85
+ throw new UnavailabilityError(
86
+ NATIVE_MODULE_NAME,
87
+ 'setSystemBrightnessAsync',
88
+ );
73
89
  }
74
90
  await expoBrightness.setSystemBrightnessAsync(clamped);
75
91
  }
@@ -84,7 +100,10 @@ export async function restoreSystemBrightnessAsync(): Promise<void> {
84
100
  return;
85
101
  }
86
102
  if (!expoBrightness.restoreSystemBrightnessAsync) {
87
- throw new UnavailabilityError(NATIVE_MODULE_NAME, 'restoreSystemBrightnessAsync');
103
+ throw new UnavailabilityError(
104
+ NATIVE_MODULE_NAME,
105
+ 'restoreSystemBrightnessAsync',
106
+ );
88
107
  }
89
108
  await expoBrightness.restoreSystemBrightnessAsync();
90
109
  }
@@ -102,7 +121,10 @@ export async function isUsingSystemBrightnessAsync(): Promise<boolean> {
102
121
  return false;
103
122
  }
104
123
  if (!expoBrightness.isUsingSystemBrightnessAsync) {
105
- throw new UnavailabilityError(NATIVE_MODULE_NAME, 'isUsingSystemBrightnessAsync');
124
+ throw new UnavailabilityError(
125
+ NATIVE_MODULE_NAME,
126
+ 'isUsingSystemBrightnessAsync',
127
+ );
106
128
  }
107
129
  return expoBrightness.isUsingSystemBrightnessAsync();
108
130
  }
@@ -117,7 +139,10 @@ export async function getSystemBrightnessModeAsync(): Promise<BrightnessMode> {
117
139
  return BrightnessMode.UNKNOWN;
118
140
  }
119
141
  if (!expoBrightness.getSystemBrightnessModeAsync) {
120
- throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getSystemBrightnessModeAsync');
142
+ throw new UnavailabilityError(
143
+ NATIVE_MODULE_NAME,
144
+ 'getSystemBrightnessModeAsync',
145
+ );
121
146
  }
122
147
  return expoBrightness.getSystemBrightnessModeAsync();
123
148
  }
@@ -127,12 +152,17 @@ export async function getSystemBrightnessModeAsync(): Promise<BrightnessMode> {
127
152
  * when passed `BrightnessMode.UNKNOWN` (there is nothing meaningful to set it to).
128
153
  * @platform android
129
154
  */
130
- export async function setSystemBrightnessModeAsync(brightnessMode: BrightnessMode): Promise<void> {
155
+ export async function setSystemBrightnessModeAsync(
156
+ brightnessMode: BrightnessMode,
157
+ ): Promise<void> {
131
158
  if (Platform.OS !== 'android' || brightnessMode === BrightnessMode.UNKNOWN) {
132
159
  return;
133
160
  }
134
161
  if (!expoBrightness.setSystemBrightnessModeAsync) {
135
- throw new UnavailabilityError(NATIVE_MODULE_NAME, 'setSystemBrightnessModeAsync');
162
+ throw new UnavailabilityError(
163
+ NATIVE_MODULE_NAME,
164
+ 'setSystemBrightnessModeAsync',
165
+ );
136
166
  }
137
167
  await expoBrightness.setSystemBrightnessModeAsync(brightnessMode);
138
168
  }
@@ -1,6 +1,10 @@
1
1
  import { requireNativeModule } from 'expo-modules-core';
2
2
  import type { EventSubscription } from 'expo-modules-core';
3
- import type { BrightnessEvent, BrightnessMode, PermissionResponse } from './types';
3
+ import type {
4
+ BrightnessEvent,
5
+ BrightnessMode,
6
+ PermissionResponse,
7
+ } from './types';
4
8
 
5
9
  const EXPO_BRIGHTNESS_MODULE_NAME = 'ExpoBrightness';
6
10
 
@@ -2,6 +2,11 @@
2
2
  // the current permission status on mount, then exposes get/request as imperative callbacks.
3
3
  // Mirrors the shape @symbiote-native/cellular's own usePermissions ships in parallel, so both
4
4
  // stay byte-for-byte consistent.
5
+ //
6
+ // The mount fetch has nobody to reject to, so its failure lands in the 4th tuple slot (`error`)
7
+ // instead of escaping as an unhandled rejection: a null status with a non-null error is how a
8
+ // consumer tells "the fetch failed" from "not fetched yet". get()/request() still reject to their
9
+ // direct caller — only the automatic call is made safe.
5
10
  import { useCallback, useEffect, useRef, useState } from 'react';
6
11
  import {
7
12
  getPermissionsAsync,
@@ -13,29 +18,40 @@ export function usePermissions(): [
13
18
  PermissionResponse | null,
14
19
  () => Promise<PermissionResponse>,
15
20
  () => Promise<PermissionResponse>,
21
+ Error | null,
16
22
  ] {
17
23
  const isMounted = useRef(true);
18
24
  const [status, setStatus] = useState<PermissionResponse | null>(null);
25
+ const [error, setError] = useState<Error | null>(null);
19
26
 
20
27
  const getPermission = useCallback(async () => {
21
28
  const response = await getPermissionsAsync();
22
- if (isMounted.current) setStatus(response);
29
+ if (isMounted.current) {
30
+ setStatus(response);
31
+ setError(null);
32
+ }
23
33
  return response;
24
34
  }, []);
25
35
 
26
36
  const requestPermission = useCallback(async () => {
27
37
  const response = await requestPermissionsAsync();
28
- if (isMounted.current) setStatus(response);
38
+ if (isMounted.current) {
39
+ setStatus(response);
40
+ setError(null);
41
+ }
29
42
  return response;
30
43
  }, []);
31
44
 
32
45
  useEffect(() => {
33
46
  isMounted.current = true;
34
- void getPermission();
47
+ getPermission().catch((cause: unknown) => {
48
+ if (isMounted.current)
49
+ setError(cause instanceof Error ? cause : new Error(String(cause)));
50
+ });
35
51
  return () => {
36
52
  isMounted.current = false;
37
53
  };
38
54
  }, [getPermission]);
39
55
 
40
- return [status, requestPermission, getPermission];
56
+ return [status, requestPermission, getPermission, error];
41
57
  }
@@ -4,23 +4,4 @@
4
4
  // mirrors the lifecycle-bucket naming convention of adapters/react/src/hooks.
5
5
 
6
6
  export { usePermissions } from './hooks/use-permissions';
7
- export {
8
- isAvailableAsync,
9
- getBrightnessAsync,
10
- setBrightnessAsync,
11
- getSystemBrightnessAsync,
12
- setSystemBrightnessAsync,
13
- restoreSystemBrightnessAsync,
14
- isUsingSystemBrightnessAsync,
15
- getSystemBrightnessModeAsync,
16
- setSystemBrightnessModeAsync,
17
- getPermissionsAsync,
18
- requestPermissionsAsync,
19
- addBrightnessListener,
20
- BrightnessMode,
21
- PermissionStatus,
22
- type BrightnessEvent,
23
- type PermissionExpiration,
24
- type PermissionResponse,
25
- type EventSubscription,
26
- } from '../core';
7
+ export * from '../core';
@@ -0,0 +1,7 @@
1
+ // @symbiote-native/brightness/solid: the Solid entry over the framework-agnostic core, plus
2
+ // createPermissions — the only stateful surface this package needs a lifecycle wrapper for. The
3
+ // lifecycle bucket is `primitives/` and the name is `create*` (never `hooks/`/`use*`, that is
4
+ // React's term and Solid's word for consuming an existing thing) — see
5
+ // adapters/solid/src/primitives for the same convention.
6
+ export { createPermissions } from './primitives/create-permissions';
7
+ export * from '../core';
@@ -0,0 +1,69 @@
1
+ // Solid lifecycle wiring over the framework-agnostic core (core/brightness.ts) — the Solid twin of
2
+ // React's usePermissions hook, Vue's composable, Svelte's rune and Angular's PermissionsService.
3
+ //
4
+ // `primitives/` and `create*`, not `hooks/`/`use*`: Solid's ecosystem calls a composable reactive
5
+ // function a PRIMITIVE and reserves `use*` for consuming something that already exists. Full
6
+ // rationale in adapters/solid/src/primitives/create-color-scheme.ts's header.
7
+ //
8
+ // Returns ACCESSORS, never a snapshot: a Solid component body runs ONCE, so a plain returned value
9
+ // would freeze at "not fetched yet" and never move again.
10
+ //
11
+ // The initial fetch is fired SYNCHRONOUSLY from the primitive body rather than from a mount hook
12
+ // (React's useEffect, Vue's onMounted, Svelte's $effect) — same reason create-color-scheme
13
+ // subscribes synchronously: nothing can interleave between construction and the fetch starting, so
14
+ // there is no window in which a consumer sees a primitive that has not asked for status yet.
15
+ //
16
+ // That fetch has nobody to reject to, so its failure lands in `error` instead of escaping as an
17
+ // unhandled rejection: a null status with a non-null error is how a consumer tells "the fetch
18
+ // failed" from "not fetched yet". get()/request() still reject to their direct caller — only the
19
+ // automatic call is made safe.
20
+ //
21
+ // `onCleanup` drops the writes of a call still in flight when the owner is disposed — the Solid
22
+ // equivalent of React's isMounted ref. Outside a component / createRoot there is no owner to hang
23
+ // it on, so those late writes land instead (harmless: nothing reads a disposed signal).
24
+ import { createSignal, onCleanup, type Accessor } from 'solid-js';
25
+ import {
26
+ getPermissionsAsync,
27
+ requestPermissionsAsync,
28
+ type PermissionResponse,
29
+ } from '../../core';
30
+
31
+ export function createPermissions(): {
32
+ status: Accessor<PermissionResponse | null>;
33
+ error: Accessor<Error | null>;
34
+ request: () => Promise<PermissionResponse>;
35
+ get: () => Promise<PermissionResponse>;
36
+ } {
37
+ const [status, setStatus] = createSignal<PermissionResponse | null>(null);
38
+ const [error, setError] = createSignal<Error | null>(null);
39
+ let isDisposed = false;
40
+
41
+ const record = (response: PermissionResponse): void => {
42
+ if (isDisposed) return;
43
+ setStatus(response);
44
+ setError(null);
45
+ };
46
+
47
+ const get = async (): Promise<PermissionResponse> => {
48
+ const response = await getPermissionsAsync();
49
+ record(response);
50
+ return response;
51
+ };
52
+
53
+ const request = async (): Promise<PermissionResponse> => {
54
+ const response = await requestPermissionsAsync();
55
+ record(response);
56
+ return response;
57
+ };
58
+
59
+ get().catch((cause: unknown) => {
60
+ if (isDisposed) return;
61
+ setError(cause instanceof Error ? cause : new Error(String(cause)));
62
+ });
63
+
64
+ onCleanup(() => {
65
+ isDisposed = true;
66
+ });
67
+
68
+ return { status, error, request, get };
69
+ }
@@ -0,0 +1,7 @@
1
+ // @symbiote-native/brightness/svelte: the Svelte entry over the framework-agnostic core. Same
2
+ // reasoning as the React and Vue entries — only the permission surface gets a lifecycle rune,
3
+ // every other export is a plain re-export — mirrors the lifecycle-bucket naming convention of
4
+ // adapters/svelte/src/runes (never `hooks/`/`composables/`, those are React's and Vue's terms).
5
+
6
+ export { usePermissions } from './runes/use-permissions.svelte';
7
+ export * from '../core';
@@ -0,0 +1,69 @@
1
+ // Svelte lifecycle wiring over the framework-agnostic core (core/brightness.ts) — auto-fetches
2
+ // the current permission status on mount, then exposes get/request as imperative callbacks. The
3
+ // Svelte twin of vue/composables/use-permissions and react/hooks/use-permissions.
4
+ //
5
+ // `.svelte.ts` (not `.ts`): runes ($state/$effect) are only usable in a file with this extension
6
+ // outside an actual `.svelte` component. `runes/` is Svelte's own term for the bucket React calls
7
+ // `hooks/` and Vue calls `composables/` — see adapters/svelte/src/runes.
8
+ //
9
+ // Vue's onMounted collapses into a `$effect` with no teardown (there is no ongoing subscription
10
+ // here, just a one-shot get). The effect only WRITES `status` (through `get()`), never reads it,
11
+ // so its dependency set stays empty and it fires exactly once on mount.
12
+ //
13
+ // `status` comes back as a GETTER on the returned object rather than a raw `$state` variable:
14
+ // Svelte 5 reactivity is lexically scoped to the declaring module, so a raw `let x = $state(...)`
15
+ // handed out of a plain function arrives dead at the caller. Reading `.status` is the Svelte
16
+ // equivalent of unwrapping Vue's `Ref` via `.value`.
17
+ //
18
+ // The mount fetch has nobody to reject to, so its failure lands in `error` (a second boxed getter,
19
+ // same reason as `status`) instead of escaping as an unhandled rejection: a null status with a
20
+ // non-null error is how a consumer tells "the fetch failed" from "not fetched yet".
21
+ // get()/request() still reject to their direct caller — only the automatic call is made safe.
22
+ import {
23
+ getPermissionsAsync,
24
+ requestPermissionsAsync,
25
+ type PermissionResponse,
26
+ } from '../../core';
27
+
28
+ export function usePermissions(): {
29
+ readonly status: PermissionResponse | null;
30
+ readonly error: Error | null;
31
+ request: () => Promise<PermissionResponse>;
32
+ get: () => Promise<PermissionResponse>;
33
+ } {
34
+ let status = $state<PermissionResponse | null>(null);
35
+ let error = $state<Error | null>(null);
36
+
37
+ const get = async (): Promise<PermissionResponse> => {
38
+ const response = await getPermissionsAsync();
39
+ status = response;
40
+ error = null;
41
+ return response;
42
+ };
43
+
44
+ const request = async (): Promise<PermissionResponse> => {
45
+ const response = await requestPermissionsAsync();
46
+ status = response;
47
+ error = null;
48
+ return response;
49
+ };
50
+
51
+ $effect(() => {
52
+ // The catch runs after the effect has settled, so writing `error` there adds no dependency and
53
+ // the effect still fires exactly once.
54
+ get().catch((cause: unknown) => {
55
+ error = cause instanceof Error ? cause : new Error(String(cause));
56
+ });
57
+ });
58
+
59
+ return {
60
+ get status(): PermissionResponse | null {
61
+ return status;
62
+ },
63
+ get error(): Error | null {
64
+ return error;
65
+ },
66
+ request,
67
+ get,
68
+ };
69
+ }