@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.
- package/README.md +57 -25
- package/build/angular/index.d.ts +1 -1
- package/build/angular/index.js +1 -1
- package/build/angular/services/permissions.service/index.d.ts +4 -0
- package/build/angular/services/permissions.service/index.js +19 -2
- package/build/core/brightness.js +1 -1
- package/build/react/hooks/use-permissions/index.d.ts +2 -1
- package/build/react/hooks/use-permissions/index.js +17 -4
- package/build/react/index.d.ts +1 -1
- package/build/react/index.js +1 -1
- package/build/solid/index.d.ts +2 -0
- package/build/solid/index.js +7 -0
- package/build/solid/primitives/create-permissions.d.ts +8 -0
- package/build/solid/primitives/create-permissions.js +55 -0
- package/build/svelte/index.d.ts +2 -0
- package/build/svelte/index.js +6 -0
- package/build/svelte/runes/use-permissions.svelte.d.ts +7 -0
- package/build/svelte/runes/use-permissions.svelte.js +55 -0
- package/build/vue/composables/use-permissions/index.d.ts +1 -0
- package/build/vue/composables/use-permissions/index.js +12 -2
- package/build/vue/index.d.ts +1 -1
- package/build/vue/index.js +1 -1
- package/build-ngc/angular/index.d.ts +1 -1
- package/build-ngc/angular/index.js +1 -1
- package/build-ngc/angular/index.js.map +1 -1
- package/build-ngc/angular/services/permissions.service/index.d.ts +4 -0
- package/build-ngc/angular/services/permissions.service/index.js +23 -5
- package/build-ngc/angular/services/permissions.service/index.js.map +1 -1
- package/build-ngc/core/brightness.js +1 -1
- package/build-ngc/core/brightness.js.map +1 -1
- package/build-ngc/core/native-module.js.map +1 -1
- package/package.json +42 -14
- package/src/angular/index.ts +1 -20
- package/src/angular/services/permissions.service/index.ts +22 -2
- package/src/core/brightness.ts +41 -11
- package/src/core/native-module.ts +5 -1
- package/src/react/hooks/use-permissions/index.ts +20 -4
- package/src/react/index.ts +1 -20
- package/src/solid/index.ts +7 -0
- package/src/solid/primitives/create-permissions.ts +69 -0
- package/src/svelte/index.ts +7 -0
- package/src/svelte/runes/use-permissions.svelte.ts +69 -0
- package/src/svelte/svelte-ambient.d.ts +6 -0
- package/src/vue/composables/use-permissions/index.ts +13 -2
- package/src/vue/index.ts +1 -20
package/README.md
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
A wrapper package for [SymbioteNative](../../README.md) that makes
|
|
4
4
|
[`expo-brightness`](https://github.com/expo/expo/tree/main/packages/expo-brightness) — screen
|
|
5
5
|
brightness get/set, Android-only system-brightness-mode control, an iOS-only brightness-change
|
|
6
|
-
listener, and permission get/request — usable from **every** adapter
|
|
7
|
-
just React. Unlike [`@symbiote-native/sensors`](../sensors) — a `DeviceSensor` class per instance,
|
|
6
|
+
listener, and permission get/request — usable from **every** adapter: React, Vue, Svelte, Solid,
|
|
7
|
+
and Angular, not just React. Unlike [`@symbiote-native/sensors`](../sensors) — a `DeviceSensor` class per instance,
|
|
8
8
|
subscribed through a hook/composable/service — this package's surface is mostly stateless free
|
|
9
9
|
async functions plus one listener; only the permission surface gets its own lifecycle hook/
|
|
10
10
|
composable/service, mirroring upstream's own `usePermissions`. Built the same way as
|
|
@@ -31,13 +31,13 @@ Unlike a plain RN native module, `expo-brightness`'s native code is discovered b
|
|
|
31
31
|
this repo use — this needs wiring into the native host app **once**, covering this package and
|
|
32
32
|
every other `expo-modules-core` package with zero further changes:
|
|
33
33
|
|
|
34
|
-
| Platform | Touches
|
|
35
|
-
|
|
36
|
-
| iOS
|
|
37
|
-
| iOS
|
|
38
|
-
| Android
|
|
39
|
-
| Android
|
|
40
|
-
| Android
|
|
34
|
+
| Platform | Touches |
|
|
35
|
+
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
36
|
+
| iOS | `ios/Podfile` — add `use_expo_modules!` |
|
|
37
|
+
| iOS | `AppDelegate.swift` — Expo's runtime-bootstrap hook |
|
|
38
|
+
| Android | `settings.gradle` / `app/build.gradle` — resolve and include the Expo Gradle projects |
|
|
39
|
+
| 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) |
|
|
40
|
+
| Android | `AndroidManifest.xml` — `android.permission.WRITE_SETTINGS`, required to set the system-wide brightness (`setSystemBrightnessAsync`/`setSystemBrightnessModeAsync`) |
|
|
41
41
|
|
|
42
42
|
Full mechanics — the Podfile pieces that normally ship inside the `expo` package, the `expo`
|
|
43
43
|
peer-dependency exclusion list — live in the `symbiote-expo-native-module` skill. Reference
|
|
@@ -55,14 +55,17 @@ src/core/ isAvailableAsync, get/setBrightnessAsync, the Android-
|
|
|
55
55
|
ExpoBrightness through expo-modules-core's requireNativeModule.
|
|
56
56
|
src/react/hooks/ @symbiote-native/brightness/react — usePermissions
|
|
57
57
|
src/vue/composables/ @symbiote-native/brightness/vue — usePermissions (same name)
|
|
58
|
+
src/svelte/runes/ @symbiote-native/brightness/svelte — usePermissions (same name)
|
|
59
|
+
src/solid/primitives/ @symbiote-native/brightness/solid — createPermissions (Solid says
|
|
60
|
+
create*, not use*)
|
|
58
61
|
src/angular/services/ @symbiote-native/brightness/angular — PermissionsService
|
|
59
62
|
```
|
|
60
63
|
|
|
61
64
|
Each adapter's hook/composable/service wraps the same one-shot `getPermissionsAsync`/
|
|
62
65
|
`requestPermissionsAsync` pair in its own lifecycle idiom (auto-fetch on mount, exposed as a
|
|
63
|
-
tuple/object/signal). Every other export — brightness get/set, the system-brightness-mode
|
|
64
|
-
the listener — is a stateless free-function re-export, written once in `core/` and shared
|
|
65
|
-
by
|
|
66
|
+
tuple/object/accessor/signal). Every other export — brightness get/set, the system-brightness-mode
|
|
67
|
+
surface, the listener — is a stateless free-function re-export, written once in `core/` and shared
|
|
68
|
+
verbatim by every adapter.
|
|
66
69
|
|
|
67
70
|
## Use it
|
|
68
71
|
|
|
@@ -84,13 +87,17 @@ function BrightnessScreen() {
|
|
|
84
87
|
useEffect(() => {
|
|
85
88
|
getBrightnessAsync().then(setBrightness);
|
|
86
89
|
// iOS only — never fires on Android, so there the value only changes via setBrightnessAsync.
|
|
87
|
-
const subscription = addBrightnessListener(event =>
|
|
90
|
+
const subscription = addBrightnessListener(event =>
|
|
91
|
+
setBrightness(event.brightness),
|
|
92
|
+
);
|
|
88
93
|
return () => subscription.remove();
|
|
89
94
|
}, []);
|
|
90
95
|
|
|
91
96
|
return (
|
|
92
97
|
<View>
|
|
93
|
-
<Text>
|
|
98
|
+
<Text>
|
|
99
|
+
{brightness === null ? 'checking…' : `${Math.round(brightness * 100)}%`}
|
|
100
|
+
</Text>
|
|
94
101
|
<Pressable onPress={() => setBrightnessAsync(0.5)}>
|
|
95
102
|
<Text>Set to 50%</Text>
|
|
96
103
|
</Pressable>
|
|
@@ -117,20 +124,25 @@ import {
|
|
|
117
124
|
import { usePermissions } from '@symbiote-native/brightness/vue';
|
|
118
125
|
|
|
119
126
|
const brightness = ref<number | null>(null);
|
|
120
|
-
const { status: permissionStatus, request: requestPermission } =
|
|
127
|
+
const { status: permissionStatus, request: requestPermission } =
|
|
128
|
+
usePermissions();
|
|
121
129
|
|
|
122
130
|
let subscription: EventSubscription | undefined;
|
|
123
131
|
|
|
124
132
|
onMounted(() => {
|
|
125
133
|
void getBrightnessAsync().then(value => (brightness.value = value));
|
|
126
|
-
subscription = addBrightnessListener(
|
|
134
|
+
subscription = addBrightnessListener(
|
|
135
|
+
event => (brightness.value = event.brightness),
|
|
136
|
+
);
|
|
127
137
|
});
|
|
128
138
|
onUnmounted(() => subscription?.remove());
|
|
129
139
|
</script>
|
|
130
140
|
|
|
131
141
|
<template>
|
|
132
142
|
<View>
|
|
133
|
-
<Text>{{
|
|
143
|
+
<Text>{{
|
|
144
|
+
brightness === null ? 'checking…' : `${Math.round(brightness * 100)}%`
|
|
145
|
+
}}</Text>
|
|
134
146
|
<Pressable @press="setBrightnessAsync(0.5)">
|
|
135
147
|
<Text>Set to 50%</Text>
|
|
136
148
|
</Pressable>
|
|
@@ -160,9 +172,13 @@ import {
|
|
|
160
172
|
template: `
|
|
161
173
|
<View>
|
|
162
174
|
<Text>{{ brightnessLabel() }}</Text>
|
|
163
|
-
<Pressable (press)="setBrightnessAsync(0.5)"
|
|
175
|
+
<Pressable (press)="setBrightnessAsync(0.5)"
|
|
176
|
+
><Text>Set to 50%</Text></Pressable
|
|
177
|
+
>
|
|
164
178
|
<Text>{{ permissionStatus()?.status ?? 'checking…' }}</Text>
|
|
165
|
-
<Pressable (press)="permissionsService.request()"
|
|
179
|
+
<Pressable (press)="permissionsService.request()"
|
|
180
|
+
><Text>Request permission</Text></Pressable
|
|
181
|
+
>
|
|
166
182
|
</View>
|
|
167
183
|
`,
|
|
168
184
|
})
|
|
@@ -182,7 +198,9 @@ export class BrightnessScreen {
|
|
|
182
198
|
}
|
|
183
199
|
|
|
184
200
|
setBrightnessAsync(value: number): void {
|
|
185
|
-
setBrightnessAsync(value).then(() =>
|
|
201
|
+
setBrightnessAsync(value).then(() =>
|
|
202
|
+
getBrightnessAsync().then(result => this.brightness.set(result)),
|
|
203
|
+
);
|
|
186
204
|
}
|
|
187
205
|
}
|
|
188
206
|
```
|
|
@@ -230,18 +248,25 @@ Plus `BrightnessMode` (enum: `UNKNOWN`/`AUTOMATIC`/`MANUAL`), `BrightnessEvent`,
|
|
|
230
248
|
verbatim from `expo-modules-core`, never the `expo` meta-package.
|
|
231
249
|
|
|
232
250
|
```ts
|
|
233
|
-
import {
|
|
251
|
+
import {
|
|
252
|
+
getBrightnessAsync,
|
|
253
|
+
setBrightnessAsync,
|
|
254
|
+
} from '@symbiote-native/brightness';
|
|
234
255
|
|
|
235
256
|
// framework-scoped entry points re-export the same free functions, plus a usePermissions
|
|
236
257
|
// hook/composable/service:
|
|
237
258
|
import { usePermissions } from '@symbiote-native/brightness/react';
|
|
238
259
|
import { usePermissions } from '@symbiote-native/brightness/vue';
|
|
260
|
+
import { usePermissions } from '@symbiote-native/brightness/svelte';
|
|
261
|
+
import { createPermissions } from '@symbiote-native/brightness/solid';
|
|
239
262
|
import { PermissionsService } from '@symbiote-native/brightness/angular';
|
|
240
263
|
```
|
|
241
264
|
|
|
242
|
-
`usePermissions` (React/Vue) auto-fetches the current permission status on mount and
|
|
243
|
-
`[status, request, get]` tuple (React) / `{ status, request, get }` object (Vue
|
|
244
|
-
|
|
265
|
+
`usePermissions` (React/Vue/Svelte) auto-fetches the current permission status on mount and
|
|
266
|
+
returns a `[status, request, get]` tuple (React) / a `{ status, request, get }` object (Vue refs,
|
|
267
|
+
Svelte getters). Solid's `createPermissions` fetches synchronously from the primitive body and
|
|
268
|
+
returns the same object with `status`/`error` as ACCESSORS; Angular's
|
|
269
|
+
`PermissionsService.connect()` returns a readonly `Signal`:
|
|
245
270
|
|
|
246
271
|
```ts
|
|
247
272
|
// React
|
|
@@ -250,6 +275,12 @@ const [status, request] = usePermissions();
|
|
|
250
275
|
// Vue
|
|
251
276
|
const { status, request } = usePermissions();
|
|
252
277
|
|
|
278
|
+
// Svelte — a boxed getter, read as permissions.status
|
|
279
|
+
const permissions = usePermissions();
|
|
280
|
+
|
|
281
|
+
// Solid — accessors, so status() is what a tracked scope reads
|
|
282
|
+
const { status, request } = createPermissions();
|
|
283
|
+
|
|
253
284
|
// Angular
|
|
254
285
|
readonly status = inject(PermissionsService).connect();
|
|
255
286
|
```
|
|
@@ -275,7 +306,8 @@ readonly status = inject(PermissionsService).connect();
|
|
|
275
306
|
|
|
276
307
|
No Fabric/Descriptor angle at all — brightness is a pure async-function + listener + permission
|
|
277
308
|
surface, never a view. Tests inject a fake native-module object in place of the real
|
|
278
|
-
`requireNativeModule` resolution (`src/core/*.test.ts`,
|
|
309
|
+
`requireNativeModule` resolution (`src/core/*.test.ts`,
|
|
310
|
+
`src/{react,vue,svelte,solid,angular}/**/*.test.{ts,tsx}`,
|
|
279
311
|
`vitest`), the same pattern `expo-brightness` itself uses upstream — no `installFabric()`, no
|
|
280
312
|
ViewConfig. Native rendering itself is verified on-device (see the parent
|
|
281
313
|
[README](../../README.md) for the project's testing model).
|
package/build/angular/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { PermissionsService } from './services/permissions.service';
|
|
2
|
-
export
|
|
2
|
+
export * from '../core';
|
package/build/angular/index.js
CHANGED
|
@@ -1,4 +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/index.js';
|
|
4
|
-
export
|
|
4
|
+
export * from '../core/index.js';
|
|
@@ -2,6 +2,10 @@ import { type Signal } from '@angular/core';
|
|
|
2
2
|
import { type PermissionResponse } from '../../../core';
|
|
3
3
|
export declare class PermissionsService {
|
|
4
4
|
private readonly status;
|
|
5
|
+
private readonly fetchError;
|
|
6
|
+
private isAutoFetchStarted;
|
|
7
|
+
/** Why the auto-fetch left `status` at null. Cleared by the next successful get()/request(). */
|
|
8
|
+
readonly error: Signal<Error | null>;
|
|
5
9
|
connect(): Signal<PermissionResponse | null>;
|
|
6
10
|
get(): Promise<PermissionResponse>;
|
|
7
11
|
request(): Promise<PermissionResponse>;
|
|
@@ -39,6 +39,11 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
|
|
|
39
39
|
//
|
|
40
40
|
// readonly status = inject(PermissionsService).connect();
|
|
41
41
|
// // template: {{ status()?.granted }}
|
|
42
|
+
//
|
|
43
|
+
// The auto-fetch has nobody to reject to, so its failure lands in the `error` signal instead of
|
|
44
|
+
// escaping as an unhandled rejection: a null status with a non-null error is how a consumer tells
|
|
45
|
+
// "the fetch failed" from "not fetched yet". get()/request() still reject to their direct caller —
|
|
46
|
+
// only the automatic call is made safe.
|
|
42
47
|
import { Injectable, signal } from '@angular/core';
|
|
43
48
|
import { getPermissionsAsync, requestPermissionsAsync, } from '../../../core/index.js';
|
|
44
49
|
let PermissionsService = (() => {
|
|
@@ -56,20 +61,32 @@ let PermissionsService = (() => {
|
|
|
56
61
|
__runInitializers(_classThis, _classExtraInitializers);
|
|
57
62
|
}
|
|
58
63
|
status = signal(null);
|
|
64
|
+
fetchError = signal(null);
|
|
65
|
+
isAutoFetchStarted = false;
|
|
66
|
+
/** Why the auto-fetch left `status` at null. Cleared by the next successful get()/request(). */
|
|
67
|
+
error = this.fetchError.asReadonly();
|
|
59
68
|
connect() {
|
|
60
|
-
|
|
61
|
-
|
|
69
|
+
// Latched rather than guarded on `status() === null`: a failed fetch leaves status null
|
|
70
|
+
// forever, so the old guard turned every later connect() into another native call — unbounded
|
|
71
|
+
// once connect() is reached from a change-detected expression instead of a field initializer.
|
|
72
|
+
if (!this.isAutoFetchStarted) {
|
|
73
|
+
this.isAutoFetchStarted = true;
|
|
74
|
+
this.get().catch((cause) => {
|
|
75
|
+
this.fetchError.set(cause instanceof Error ? cause : new Error(String(cause)));
|
|
76
|
+
});
|
|
62
77
|
}
|
|
63
78
|
return this.status.asReadonly();
|
|
64
79
|
}
|
|
65
80
|
async get() {
|
|
66
81
|
const response = await getPermissionsAsync();
|
|
67
82
|
this.status.set(response);
|
|
83
|
+
this.fetchError.set(null);
|
|
68
84
|
return response;
|
|
69
85
|
}
|
|
70
86
|
async request() {
|
|
71
87
|
const response = await requestPermissionsAsync();
|
|
72
88
|
this.status.set(response);
|
|
89
|
+
this.fetchError.set(null);
|
|
73
90
|
return response;
|
|
74
91
|
}
|
|
75
92
|
};
|
package/build/core/brightness.js
CHANGED
|
@@ -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.js';
|
|
7
|
-
import { BrightnessMode } from './types.js';
|
|
7
|
+
import { BrightnessMode, } from './types.js';
|
|
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));
|
|
@@ -2,29 +2,42 @@
|
|
|
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 { getPermissionsAsync, requestPermissionsAsync, } from '../../../core/index.js';
|
|
7
12
|
export function usePermissions() {
|
|
8
13
|
const isMounted = useRef(true);
|
|
9
14
|
const [status, setStatus] = useState(null);
|
|
15
|
+
const [error, setError] = useState(null);
|
|
10
16
|
const getPermission = useCallback(async () => {
|
|
11
17
|
const response = await getPermissionsAsync();
|
|
12
|
-
if (isMounted.current)
|
|
18
|
+
if (isMounted.current) {
|
|
13
19
|
setStatus(response);
|
|
20
|
+
setError(null);
|
|
21
|
+
}
|
|
14
22
|
return response;
|
|
15
23
|
}, []);
|
|
16
24
|
const requestPermission = useCallback(async () => {
|
|
17
25
|
const response = await requestPermissionsAsync();
|
|
18
|
-
if (isMounted.current)
|
|
26
|
+
if (isMounted.current) {
|
|
19
27
|
setStatus(response);
|
|
28
|
+
setError(null);
|
|
29
|
+
}
|
|
20
30
|
return response;
|
|
21
31
|
}, []);
|
|
22
32
|
useEffect(() => {
|
|
23
33
|
isMounted.current = true;
|
|
24
|
-
|
|
34
|
+
getPermission().catch((cause) => {
|
|
35
|
+
if (isMounted.current)
|
|
36
|
+
setError(cause instanceof Error ? cause : new Error(String(cause)));
|
|
37
|
+
});
|
|
25
38
|
return () => {
|
|
26
39
|
isMounted.current = false;
|
|
27
40
|
};
|
|
28
41
|
}, [getPermission]);
|
|
29
|
-
return [status, requestPermission, getPermission];
|
|
42
|
+
return [status, requestPermission, getPermission, error];
|
|
30
43
|
}
|
package/build/react/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { usePermissions } from './hooks/use-permissions';
|
|
2
|
-
export
|
|
2
|
+
export * from '../core';
|
package/build/react/index.js
CHANGED
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
// every other export is a stateless async function or one-shot listener, re-exported verbatim —
|
|
4
4
|
// mirrors the lifecycle-bucket naming convention of adapters/react/src/hooks.
|
|
5
5
|
export { usePermissions } from './hooks/use-permissions/index.js';
|
|
6
|
-
export
|
|
6
|
+
export * from '../core/index.js';
|
|
@@ -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.js';
|
|
7
|
+
export * from '../core/index.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Accessor } from 'solid-js';
|
|
2
|
+
import { type PermissionResponse } from '../../core';
|
|
3
|
+
export declare function createPermissions(): {
|
|
4
|
+
status: Accessor<PermissionResponse | null>;
|
|
5
|
+
error: Accessor<Error | null>;
|
|
6
|
+
request: () => Promise<PermissionResponse>;
|
|
7
|
+
get: () => Promise<PermissionResponse>;
|
|
8
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
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 } from 'solid-js';
|
|
25
|
+
import { getPermissionsAsync, requestPermissionsAsync, } from '../../core/index.js';
|
|
26
|
+
export function createPermissions() {
|
|
27
|
+
const [status, setStatus] = createSignal(null);
|
|
28
|
+
const [error, setError] = createSignal(null);
|
|
29
|
+
let isDisposed = false;
|
|
30
|
+
const record = (response) => {
|
|
31
|
+
if (isDisposed)
|
|
32
|
+
return;
|
|
33
|
+
setStatus(response);
|
|
34
|
+
setError(null);
|
|
35
|
+
};
|
|
36
|
+
const get = async () => {
|
|
37
|
+
const response = await getPermissionsAsync();
|
|
38
|
+
record(response);
|
|
39
|
+
return response;
|
|
40
|
+
};
|
|
41
|
+
const request = async () => {
|
|
42
|
+
const response = await requestPermissionsAsync();
|
|
43
|
+
record(response);
|
|
44
|
+
return response;
|
|
45
|
+
};
|
|
46
|
+
get().catch((cause) => {
|
|
47
|
+
if (isDisposed)
|
|
48
|
+
return;
|
|
49
|
+
setError(cause instanceof Error ? cause : new Error(String(cause)));
|
|
50
|
+
});
|
|
51
|
+
onCleanup(() => {
|
|
52
|
+
isDisposed = true;
|
|
53
|
+
});
|
|
54
|
+
return { status, error, request, get };
|
|
55
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
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
|
+
export { usePermissions } from './runes/use-permissions.svelte';
|
|
6
|
+
export * from '../core/index.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type PermissionResponse } from '../../core';
|
|
2
|
+
export declare function usePermissions(): {
|
|
3
|
+
readonly status: PermissionResponse | null;
|
|
4
|
+
readonly error: Error | null;
|
|
5
|
+
request: () => Promise<PermissionResponse>;
|
|
6
|
+
get: () => Promise<PermissionResponse>;
|
|
7
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
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 { getPermissionsAsync, requestPermissionsAsync, } from '../../core/index.js';
|
|
23
|
+
export function usePermissions() {
|
|
24
|
+
let status = $state(null);
|
|
25
|
+
let error = $state(null);
|
|
26
|
+
const get = async () => {
|
|
27
|
+
const response = await getPermissionsAsync();
|
|
28
|
+
status = response;
|
|
29
|
+
error = null;
|
|
30
|
+
return response;
|
|
31
|
+
};
|
|
32
|
+
const request = async () => {
|
|
33
|
+
const response = await requestPermissionsAsync();
|
|
34
|
+
status = response;
|
|
35
|
+
error = null;
|
|
36
|
+
return response;
|
|
37
|
+
};
|
|
38
|
+
$effect(() => {
|
|
39
|
+
// The catch runs after the effect has settled, so writing `error` there adds no dependency and
|
|
40
|
+
// the effect still fires exactly once.
|
|
41
|
+
get().catch((cause) => {
|
|
42
|
+
error = cause instanceof Error ? cause : new Error(String(cause));
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
get status() {
|
|
47
|
+
return status;
|
|
48
|
+
},
|
|
49
|
+
get error() {
|
|
50
|
+
return error;
|
|
51
|
+
},
|
|
52
|
+
request,
|
|
53
|
+
get,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -2,6 +2,7 @@ import { type Ref } from '@vue/runtime-core';
|
|
|
2
2
|
import { type PermissionResponse } from '../../../core';
|
|
3
3
|
export declare function usePermissions(): {
|
|
4
4
|
status: Ref<PermissionResponse | null>;
|
|
5
|
+
error: Ref<Error | null>;
|
|
5
6
|
request: () => Promise<PermissionResponse>;
|
|
6
7
|
get: () => Promise<PermissionResponse>;
|
|
7
8
|
};
|
|
@@ -2,22 +2,32 @@
|
|
|
2
2
|
// current permission status on mount (mirrors use-battery-level's onMounted/onUnmounted style,
|
|
3
3
|
// but there is no ongoing subscription to tear down here, just a one-shot get), then exposes
|
|
4
4
|
// get/request as imperative callbacks.
|
|
5
|
+
//
|
|
6
|
+
// The mount fetch has nobody to reject to, so its failure lands in the `error` ref instead of
|
|
7
|
+
// escaping as an unhandled rejection: a null status with a non-null error is how a consumer tells
|
|
8
|
+
// "the fetch failed" from "not fetched yet". get()/request() still reject to their direct caller —
|
|
9
|
+
// only the automatic call is made safe.
|
|
5
10
|
import { onMounted, ref } from '@vue/runtime-core';
|
|
6
11
|
import { getPermissionsAsync, requestPermissionsAsync, } from '../../../core/index.js';
|
|
7
12
|
export function usePermissions() {
|
|
8
13
|
const status = ref(null);
|
|
14
|
+
const error = ref(null);
|
|
9
15
|
const get = async () => {
|
|
10
16
|
const response = await getPermissionsAsync();
|
|
11
17
|
status.value = response;
|
|
18
|
+
error.value = null;
|
|
12
19
|
return response;
|
|
13
20
|
};
|
|
14
21
|
const request = async () => {
|
|
15
22
|
const response = await requestPermissionsAsync();
|
|
16
23
|
status.value = response;
|
|
24
|
+
error.value = null;
|
|
17
25
|
return response;
|
|
18
26
|
};
|
|
19
27
|
onMounted(() => {
|
|
20
|
-
|
|
28
|
+
get().catch((cause) => {
|
|
29
|
+
error.value = cause instanceof Error ? cause : new Error(String(cause));
|
|
30
|
+
});
|
|
21
31
|
});
|
|
22
|
-
return { status, request, get };
|
|
32
|
+
return { status, error, request, get };
|
|
23
33
|
}
|
package/build/vue/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { usePermissions } from './composables/use-permissions';
|
|
2
|
-
export
|
|
2
|
+
export * from '../core';
|
package/build/vue/index.js
CHANGED
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
// other export is a plain re-export — mirrors the lifecycle-bucket naming convention of
|
|
4
4
|
// adapters/vue/src/composables (never `hooks/`, that's React's term).
|
|
5
5
|
export { usePermissions } from './composables/use-permissions/index.js';
|
|
6
|
-
export
|
|
6
|
+
export * from '../core/index.js';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { PermissionsService } from './services/permissions.service';
|
|
2
|
-
export
|
|
2
|
+
export * from '../core';
|
|
@@ -1,5 +1,5 @@
|
|
|
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
|
|
4
|
+
export * from '../core';
|
|
5
5
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/angular/index.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,iEAAiE;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/angular/index.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,iEAAiE;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,cAAc,SAAS,CAAC"}
|
|
@@ -3,6 +3,10 @@ import { type PermissionResponse } from '../../../core';
|
|
|
3
3
|
import * as i0 from "@angular/core";
|
|
4
4
|
export declare class PermissionsService {
|
|
5
5
|
private readonly status;
|
|
6
|
+
private readonly fetchError;
|
|
7
|
+
private isAutoFetchStarted;
|
|
8
|
+
/** Why the auto-fetch left `status` at null. Cleared by the next successful get()/request(). */
|
|
9
|
+
readonly error: Signal<Error | null>;
|
|
6
10
|
connect(): Signal<PermissionResponse | null>;
|
|
7
11
|
get(): Promise<PermissionResponse>;
|
|
8
12
|
request(): Promise<PermissionResponse>;
|