@usefy/use-permission 0.20.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 ADDED
@@ -0,0 +1,124 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/mirunamu00/usefy/master/assets/logo.png" alt="usefy logo" width="120" />
3
+ </p>
4
+
5
+ <h1 align="center">@usefy/use-permission</h1>
6
+
7
+ <p align="center">
8
+ <strong>Permissions API status with live updates — SSR-safe, typed, and race-safe.</strong>
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://www.npmjs.com/package/@usefy/use-permission"><img src="https://img.shields.io/npm/v/@usefy/use-permission.svg?style=flat-square&color=007acc" alt="npm version" /></a>
13
+ <a href="https://www.npmjs.com/package/@usefy/use-permission"><img src="https://img.shields.io/npm/dm/@usefy/use-permission.svg?style=flat-square&color=007acc" alt="npm downloads" /></a>
14
+ <a href="https://bundlephobia.com/package/@usefy/use-permission"><img src="https://img.shields.io/bundlephobia/minzip/@usefy/use-permission?style=flat-square&color=007acc" alt="bundle size" /></a>
15
+ <a href="https://github.com/mirunamu00/usefy/blob/master/LICENSE"><img src="https://img.shields.io/npm/l/@usefy/use-permission.svg?style=flat-square&color=007acc" alt="license" /></a>
16
+ </p>
17
+
18
+ <p align="center">
19
+ <a href="#installation">Installation</a> •
20
+ <a href="#quick-start">Quick Start</a> •
21
+ <a href="#api">API</a> •
22
+ <a href="#testing">Testing</a> •
23
+ <a href="#license">License</a>
24
+ </p>
25
+
26
+ <p align="center">
27
+ <a href="https://mirunamu00.github.io/usefy/?path=/docs/hooks-usepermission--docs" target="_blank" rel="noopener noreferrer">
28
+ <strong>📚 View Storybook Demo</strong>
29
+ </a>
30
+ </p>
31
+
32
+ ---
33
+
34
+ ## Overview
35
+
36
+ `usePermission` is part of the [@usefy](https://www.npmjs.com/org/usefy) ecosystem — a collection of production-ready, TypeScript-first, SSR-safe React hooks. It reads the [Permissions API](https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API) status of a permission and keeps it up to date live, so your UI reacts when the user grants or revokes access.
37
+
38
+ ## Features
39
+
40
+ - **Live updates** — subscribes to the `PermissionStatus` `change` event, so `state` reflects grant/revoke without polling or a re-mount.
41
+ - **Rich, honest return** — `{ state, status, isSupported, error }` makes the async, unsupported, and error paths explicit instead of hiding them behind a bare `PermissionState`.
42
+ - **Any permission name** — accepts a superset of `PermissionDescriptor`, so `camera`, `microphone`, `push`, `midi`, and future names all typecheck.
43
+ - **Stable descriptor identity** — keyed on the descriptor's serialized contents, so an inline `{ name: 'camera' }` literal does not re-query every render (no caller-side `useMemo`).
44
+ - **SSR-safe & StrictMode-safe** — reports `unsupported` on the server; the async query is race-guarded and the change listener is cleaned up on unmount.
45
+ - **TypeScript-first** — full type inference and exported types.
46
+ - **Tiny & tree-shakeable** — zero dependencies, published as its own package.
47
+
48
+ ## Installation
49
+
50
+ ```bash
51
+ # npm
52
+ npm install @usefy/use-permission
53
+
54
+ # yarn
55
+ yarn add @usefy/use-permission
56
+
57
+ # pnpm
58
+ pnpm add @usefy/use-permission
59
+ ```
60
+
61
+ Requires React 18 or 19 (`peerDependencies: "react": "^18.0.0 || ^19.0.0"`).
62
+
63
+ ## Quick Start
64
+
65
+ ```tsx
66
+ import { usePermission } from "@usefy/use-permission";
67
+
68
+ function CameraStatus() {
69
+ const { state, status } = usePermission({ name: "camera" });
70
+
71
+ // Branch on `status` (deterministic "idle" on the first render, both on the
72
+ // server and client) so the UI is hydration-safe.
73
+ if (status === "idle" || status === "pending") return <span>Checking…</span>;
74
+ if (status === "unsupported") return <span>Permissions API unavailable</span>;
75
+ if (status === "error") return <span>Could not read camera permission</span>;
76
+
77
+ return <span>Camera: {state}</span>; // 'granted' | 'denied' | 'prompt'
78
+ }
79
+ ```
80
+
81
+ ## API
82
+
83
+ ### `usePermission(descriptor)`
84
+
85
+ ```ts
86
+ function usePermission(descriptor: UsePermissionDescriptor): UsePermissionReturn;
87
+ ```
88
+
89
+ #### Parameters
90
+
91
+ | Param | Type | Description |
92
+ | ----- | ---- | ----------- |
93
+ | `descriptor` | `UsePermissionDescriptor` | The permission to query, e.g. `{ name: 'camera' }`, `{ name: 'geolocation' }`, `{ name: 'push', userVisibleOnly: true }`, `{ name: 'midi', sysex: true }`. `name` accepts the standard `PermissionName` values (with autocomplete) plus any browser-specific string. Pass an inline literal freely — the hook keys re-queries on the descriptor's serialized contents, not its object identity. |
94
+
95
+ #### Returns — `UsePermissionReturn`
96
+
97
+ | Field | Type | Description |
98
+ | ----- | ---- | ----------- |
99
+ | `state` | `PermissionState \| null` | The raw permission state (`'granted' \| 'denied' \| 'prompt'`), or `null` until the first query resolves and whenever the API is unsupported or the query errored. |
100
+ | `status` | `UsePermissionStatus` | Coarse lifecycle: `'idle' \| 'pending' \| 'granted' \| 'denied' \| 'prompt' \| 'unsupported' \| 'error'`. The three permission values mirror `state`, so you can branch on either. |
101
+ | `isSupported` | `boolean` | Whether `navigator.permissions.query` is available. Starts `false` (on the server and the first client render, for hydration safety) and becomes `true` after mount in a supporting browser. Branch your first render on `status` (`'idle'`), not `isSupported`. |
102
+ | `error` | `Error \| null` | The error thrown by `navigator.permissions.query()`, if the query rejected (e.g. an unknown permission name in a browser that throws). |
103
+
104
+ #### Helpers
105
+
106
+ - `isPermissionsSupported(): boolean` — capability check used internally; exported for feature-detection.
107
+ - `serializeDescriptor(descriptor): string` — the stable-key function the hook uses to decide when to re-query (exported for advanced use; not part of the umbrella surface).
108
+
109
+ ### Notes on behavior
110
+
111
+ - **Descriptor identity.** `usePermission({ name: 'camera' })` receives a fresh object literal every render. Rather than require callers to `useMemo` it, the hook derives a stable string key from the descriptor's own fields (sorted, JSON-serialized) and keys its effect on that. It re-queries only when the meaningful contents change, e.g. `name` `'camera'` → `'microphone'` or `userVisibleOnly` `true` → `false`.
112
+ - **Unsupported.** If `navigator.permissions` is missing (SSR or an unsupporting browser), `status` is `'unsupported'`, `isSupported` is `false`, and `state` is `null`.
113
+ - **Errors.** Some browsers reject `query()` for permission names they don't recognize (e.g. `camera` in Firefox) instead of returning `'denied'`. That surfaces as `status: 'error'` with the thrown `error`.
114
+ - **Race safety.** If the component unmounts while a query is in flight, the stale resolution/rejection is ignored — no state update after unmount — and the `change` listener is always removed on cleanup.
115
+
116
+ ## Testing
117
+
118
+ 📊 <a href="https://mirunamu00.github.io/usefy/coverage/use-permission/src/index.html" target="_blank" rel="noopener noreferrer"><strong>View Detailed Coverage Report</strong></a> (GitHub Pages) — **18 tests**, 100% statement coverage.
119
+
120
+ ## License
121
+
122
+ MIT © [mirunamu](https://github.com/mirunamu00)
123
+
124
+ This package is part of the [usefy](https://github.com/mirunamu00/usefy) monorepo.
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Coarse lifecycle status for a permission query.
3
+ *
4
+ * - `idle` — no query has resolved yet (initial render, before the effect runs).
5
+ * - `pending` — a `navigator.permissions.query()` call is in flight.
6
+ * - `granted` / `denied` / `prompt` — the resolved {@link PermissionState}. These
7
+ * mirror the raw `state` field, so you can branch on either.
8
+ * - `unsupported` — the Permissions API is unavailable in this environment
9
+ * (SSR, or a browser without `navigator.permissions`).
10
+ * - `error` — the query rejected (e.g. an unknown permission name in a browser
11
+ * that throws instead of returning `denied`). See {@link UsePermissionReturn.error}.
12
+ */
13
+ type UsePermissionStatus = "idle" | "pending" | "granted" | "denied" | "prompt" | "unsupported" | "error";
14
+ /**
15
+ * Permission descriptor accepted by {@link usePermission}.
16
+ *
17
+ * A superset of the DOM `PermissionDescriptor` so that *all* permission names
18
+ * typecheck — including the ones missing from the standard `PermissionName`
19
+ * union in `lib.dom` (`'camera'`, `'microphone'`, `'clipboard-read'`, …) and the
20
+ * extra fields required by specific permissions (`userVisibleOnly` for `'push'`,
21
+ * `sysex` for `'midi'`, device fields for `'camera'`). The index signature keeps
22
+ * it forward-compatible with descriptor fields not yet in the lib types.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const camera: UsePermissionDescriptor = { name: "camera" };
27
+ * const push: UsePermissionDescriptor = { name: "push", userVisibleOnly: true };
28
+ * const midi: UsePermissionDescriptor = { name: "midi", sysex: true };
29
+ * ```
30
+ */
31
+ interface UsePermissionDescriptor {
32
+ /**
33
+ * The permission name. Accepts the standard `PermissionName` values with
34
+ * autocomplete, plus any other string (e.g. `'camera'`, `'microphone'`) that a
35
+ * given browser supports.
36
+ */
37
+ name: PermissionName | (string & {});
38
+ /** Required for the `'push'` permission — must be `true`, browsers reject `false`. */
39
+ userVisibleOnly?: boolean;
40
+ /** Requests SysEx access for the `'midi'` permission. */
41
+ sysex?: boolean;
42
+ /** Forward-compatible with descriptor fields not yet in the lib types. */
43
+ [key: string]: unknown;
44
+ }
45
+ /**
46
+ * Return value of {@link usePermission}.
47
+ *
48
+ * Destructure the field you need — the roadmap's `const { state } = usePermission(...)`
49
+ * reads naturally, while `status`, `isSupported`, and `error` cover the async and
50
+ * unsupported/error edges that a bare `PermissionState` cannot express.
51
+ */
52
+ interface UsePermissionReturn {
53
+ /**
54
+ * The raw permission state (`'granted' | 'denied' | 'prompt'`), or `null` until
55
+ * the first query resolves and whenever the API is unsupported or the query
56
+ * errored.
57
+ */
58
+ state: PermissionState | null;
59
+ /** Coarse lifecycle status. See {@link UsePermissionStatus}. */
60
+ status: UsePermissionStatus;
61
+ /** Whether the Permissions API is available in the current environment. */
62
+ isSupported: boolean;
63
+ /** The error thrown by `navigator.permissions.query()`, if the query rejected. */
64
+ error: Error | null;
65
+ }
66
+
67
+ /**
68
+ * Track the status of a Permissions API permission, with live updates.
69
+ *
70
+ * Calls `navigator.permissions.query(descriptor)` and subscribes to the returned
71
+ * `PermissionStatus`'s `change` event, so the returned `state` reflects the user
72
+ * granting or revoking the permission without a re-mount. The hook is SSR-safe
73
+ * (reports `unsupported` on the server) and StrictMode/concurrent-safe (the async
74
+ * query is race-guarded and the listener is cleaned up on unmount).
75
+ *
76
+ * **Descriptor identity:** the effect is keyed on a *serialized* copy of the
77
+ * descriptor's fields (`name`, `userVisibleOnly`, `sysex`, …), not on the object
78
+ * reference. So passing an inline literal — `usePermission({ name: 'camera' })` —
79
+ * does not re-query on every render; it re-queries only when the descriptor's
80
+ * contents actually change. No `useMemo` on the caller's side is required.
81
+ *
82
+ * @param descriptor - The permission to query, e.g. `{ name: 'camera' }`,
83
+ * `{ name: 'geolocation' }`, `{ name: 'push', userVisibleOnly: true }`,
84
+ * `{ name: 'midi', sysex: true }`. Accepts any permission name (the standard
85
+ * ones plus browser-specific ones like `'camera'`/`'microphone'`).
86
+ * @returns `{ state, status, isSupported, error }` — see {@link UsePermissionReturn}.
87
+ *
88
+ * @example
89
+ * ```tsx
90
+ * function CameraBadge() {
91
+ * const { state, status } = usePermission({ name: "camera" });
92
+ *
93
+ * // Branch on `status` (deterministic "idle" on the first render, both on the
94
+ * // server and client) so the UI is hydration-safe.
95
+ * if (status === "idle" || status === "pending") return <span>Checking…</span>;
96
+ * if (status === "unsupported") return <span>Permissions API unavailable</span>;
97
+ * if (status === "error") return <span>Could not read camera permission</span>;
98
+ *
99
+ * return <span>Camera: {state}</span>; // 'granted' | 'denied' | 'prompt'
100
+ * }
101
+ * ```
102
+ *
103
+ * @example
104
+ * ```tsx
105
+ * // Live updates: the badge re-renders when the user changes the permission in
106
+ * // the browser UI — no polling, no re-mount.
107
+ * function MicIndicator() {
108
+ * const { state } = usePermission({ name: "microphone" });
109
+ * return <div data-granted={state === "granted"}>Mic: {state ?? "unknown"}</div>;
110
+ * }
111
+ * ```
112
+ */
113
+ declare function usePermission(descriptor: UsePermissionDescriptor): UsePermissionReturn;
114
+
115
+ /**
116
+ * Whether the Permissions API (`navigator.permissions.query`) is available in the
117
+ * current environment. Returns `false` during SSR (no `navigator`) and in browsers
118
+ * that do not implement the Permissions API.
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * if (isPermissionsSupported()) {
123
+ * // safe to call navigator.permissions.query(...)
124
+ * }
125
+ * ```
126
+ */
127
+ declare function isPermissionsSupported(): boolean;
128
+ /**
129
+ * Produce a stable string key from a permission descriptor's meaningful fields.
130
+ *
131
+ * `usePermission` keys its effect on this string, not on the descriptor object's
132
+ * reference — so an inline `usePermission({ name: 'camera' })` (a fresh object
133
+ * literal every render) does **not** re-query on every render. It re-queries only
134
+ * when the descriptor's actual contents change. Keys are sorted so field order in
135
+ * the literal does not affect the result.
136
+ *
137
+ * @example
138
+ * ```ts
139
+ * serializeDescriptor({ name: "midi", sysex: true }); // '{"name":"midi","sysex":true}'
140
+ * serializeDescriptor({ sysex: true, name: "midi" }); // same string
141
+ * ```
142
+ */
143
+ declare function serializeDescriptor(descriptor: UsePermissionDescriptor): string;
144
+
145
+ export { type UsePermissionDescriptor, type UsePermissionReturn, type UsePermissionStatus, isPermissionsSupported, serializeDescriptor, usePermission };
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Coarse lifecycle status for a permission query.
3
+ *
4
+ * - `idle` — no query has resolved yet (initial render, before the effect runs).
5
+ * - `pending` — a `navigator.permissions.query()` call is in flight.
6
+ * - `granted` / `denied` / `prompt` — the resolved {@link PermissionState}. These
7
+ * mirror the raw `state` field, so you can branch on either.
8
+ * - `unsupported` — the Permissions API is unavailable in this environment
9
+ * (SSR, or a browser without `navigator.permissions`).
10
+ * - `error` — the query rejected (e.g. an unknown permission name in a browser
11
+ * that throws instead of returning `denied`). See {@link UsePermissionReturn.error}.
12
+ */
13
+ type UsePermissionStatus = "idle" | "pending" | "granted" | "denied" | "prompt" | "unsupported" | "error";
14
+ /**
15
+ * Permission descriptor accepted by {@link usePermission}.
16
+ *
17
+ * A superset of the DOM `PermissionDescriptor` so that *all* permission names
18
+ * typecheck — including the ones missing from the standard `PermissionName`
19
+ * union in `lib.dom` (`'camera'`, `'microphone'`, `'clipboard-read'`, …) and the
20
+ * extra fields required by specific permissions (`userVisibleOnly` for `'push'`,
21
+ * `sysex` for `'midi'`, device fields for `'camera'`). The index signature keeps
22
+ * it forward-compatible with descriptor fields not yet in the lib types.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const camera: UsePermissionDescriptor = { name: "camera" };
27
+ * const push: UsePermissionDescriptor = { name: "push", userVisibleOnly: true };
28
+ * const midi: UsePermissionDescriptor = { name: "midi", sysex: true };
29
+ * ```
30
+ */
31
+ interface UsePermissionDescriptor {
32
+ /**
33
+ * The permission name. Accepts the standard `PermissionName` values with
34
+ * autocomplete, plus any other string (e.g. `'camera'`, `'microphone'`) that a
35
+ * given browser supports.
36
+ */
37
+ name: PermissionName | (string & {});
38
+ /** Required for the `'push'` permission — must be `true`, browsers reject `false`. */
39
+ userVisibleOnly?: boolean;
40
+ /** Requests SysEx access for the `'midi'` permission. */
41
+ sysex?: boolean;
42
+ /** Forward-compatible with descriptor fields not yet in the lib types. */
43
+ [key: string]: unknown;
44
+ }
45
+ /**
46
+ * Return value of {@link usePermission}.
47
+ *
48
+ * Destructure the field you need — the roadmap's `const { state } = usePermission(...)`
49
+ * reads naturally, while `status`, `isSupported`, and `error` cover the async and
50
+ * unsupported/error edges that a bare `PermissionState` cannot express.
51
+ */
52
+ interface UsePermissionReturn {
53
+ /**
54
+ * The raw permission state (`'granted' | 'denied' | 'prompt'`), or `null` until
55
+ * the first query resolves and whenever the API is unsupported or the query
56
+ * errored.
57
+ */
58
+ state: PermissionState | null;
59
+ /** Coarse lifecycle status. See {@link UsePermissionStatus}. */
60
+ status: UsePermissionStatus;
61
+ /** Whether the Permissions API is available in the current environment. */
62
+ isSupported: boolean;
63
+ /** The error thrown by `navigator.permissions.query()`, if the query rejected. */
64
+ error: Error | null;
65
+ }
66
+
67
+ /**
68
+ * Track the status of a Permissions API permission, with live updates.
69
+ *
70
+ * Calls `navigator.permissions.query(descriptor)` and subscribes to the returned
71
+ * `PermissionStatus`'s `change` event, so the returned `state` reflects the user
72
+ * granting or revoking the permission without a re-mount. The hook is SSR-safe
73
+ * (reports `unsupported` on the server) and StrictMode/concurrent-safe (the async
74
+ * query is race-guarded and the listener is cleaned up on unmount).
75
+ *
76
+ * **Descriptor identity:** the effect is keyed on a *serialized* copy of the
77
+ * descriptor's fields (`name`, `userVisibleOnly`, `sysex`, …), not on the object
78
+ * reference. So passing an inline literal — `usePermission({ name: 'camera' })` —
79
+ * does not re-query on every render; it re-queries only when the descriptor's
80
+ * contents actually change. No `useMemo` on the caller's side is required.
81
+ *
82
+ * @param descriptor - The permission to query, e.g. `{ name: 'camera' }`,
83
+ * `{ name: 'geolocation' }`, `{ name: 'push', userVisibleOnly: true }`,
84
+ * `{ name: 'midi', sysex: true }`. Accepts any permission name (the standard
85
+ * ones plus browser-specific ones like `'camera'`/`'microphone'`).
86
+ * @returns `{ state, status, isSupported, error }` — see {@link UsePermissionReturn}.
87
+ *
88
+ * @example
89
+ * ```tsx
90
+ * function CameraBadge() {
91
+ * const { state, status } = usePermission({ name: "camera" });
92
+ *
93
+ * // Branch on `status` (deterministic "idle" on the first render, both on the
94
+ * // server and client) so the UI is hydration-safe.
95
+ * if (status === "idle" || status === "pending") return <span>Checking…</span>;
96
+ * if (status === "unsupported") return <span>Permissions API unavailable</span>;
97
+ * if (status === "error") return <span>Could not read camera permission</span>;
98
+ *
99
+ * return <span>Camera: {state}</span>; // 'granted' | 'denied' | 'prompt'
100
+ * }
101
+ * ```
102
+ *
103
+ * @example
104
+ * ```tsx
105
+ * // Live updates: the badge re-renders when the user changes the permission in
106
+ * // the browser UI — no polling, no re-mount.
107
+ * function MicIndicator() {
108
+ * const { state } = usePermission({ name: "microphone" });
109
+ * return <div data-granted={state === "granted"}>Mic: {state ?? "unknown"}</div>;
110
+ * }
111
+ * ```
112
+ */
113
+ declare function usePermission(descriptor: UsePermissionDescriptor): UsePermissionReturn;
114
+
115
+ /**
116
+ * Whether the Permissions API (`navigator.permissions.query`) is available in the
117
+ * current environment. Returns `false` during SSR (no `navigator`) and in browsers
118
+ * that do not implement the Permissions API.
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * if (isPermissionsSupported()) {
123
+ * // safe to call navigator.permissions.query(...)
124
+ * }
125
+ * ```
126
+ */
127
+ declare function isPermissionsSupported(): boolean;
128
+ /**
129
+ * Produce a stable string key from a permission descriptor's meaningful fields.
130
+ *
131
+ * `usePermission` keys its effect on this string, not on the descriptor object's
132
+ * reference — so an inline `usePermission({ name: 'camera' })` (a fresh object
133
+ * literal every render) does **not** re-query on every render. It re-queries only
134
+ * when the descriptor's actual contents change. Keys are sorted so field order in
135
+ * the literal does not affect the result.
136
+ *
137
+ * @example
138
+ * ```ts
139
+ * serializeDescriptor({ name: "midi", sysex: true }); // '{"name":"midi","sysex":true}'
140
+ * serializeDescriptor({ sysex: true, name: "midi" }); // same string
141
+ * ```
142
+ */
143
+ declare function serializeDescriptor(descriptor: UsePermissionDescriptor): string;
144
+
145
+ export { type UsePermissionDescriptor, type UsePermissionReturn, type UsePermissionStatus, isPermissionsSupported, serializeDescriptor, usePermission };
package/dist/index.js ADDED
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ isPermissionsSupported: () => isPermissionsSupported,
24
+ serializeDescriptor: () => serializeDescriptor,
25
+ usePermission: () => usePermission
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/usePermission.ts
30
+ var import_react = require("react");
31
+
32
+ // src/utils.ts
33
+ function isPermissionsSupported() {
34
+ return typeof navigator !== "undefined" && typeof navigator.permissions !== "undefined" && typeof navigator.permissions.query === "function";
35
+ }
36
+ function serializeDescriptor(descriptor) {
37
+ const keys = Object.keys(descriptor).sort();
38
+ const normalized = {};
39
+ for (const key of keys) {
40
+ normalized[key] = descriptor[key];
41
+ }
42
+ return JSON.stringify(normalized);
43
+ }
44
+
45
+ // src/usePermission.ts
46
+ function usePermission(descriptor) {
47
+ const [state, setState] = (0, import_react.useState)(null);
48
+ const [status, setStatus] = (0, import_react.useState)("idle");
49
+ const [error, setError] = (0, import_react.useState)(null);
50
+ const [isSupported, setIsSupported] = (0, import_react.useState)(false);
51
+ const descriptorRef = (0, import_react.useRef)(descriptor);
52
+ descriptorRef.current = descriptor;
53
+ const serialized = serializeDescriptor(descriptor);
54
+ (0, import_react.useEffect)(() => {
55
+ const supported = isPermissionsSupported();
56
+ setIsSupported(supported);
57
+ if (!supported) {
58
+ setState(null);
59
+ setStatus("unsupported");
60
+ setError(null);
61
+ return;
62
+ }
63
+ let cancelled = false;
64
+ let permissionStatus = null;
65
+ let changeHandler = null;
66
+ setState(null);
67
+ setStatus("pending");
68
+ setError(null);
69
+ navigator.permissions.query(descriptorRef.current).then((result) => {
70
+ if (cancelled) return;
71
+ permissionStatus = result;
72
+ setState(result.state);
73
+ setStatus(result.state);
74
+ changeHandler = () => {
75
+ setState(result.state);
76
+ setStatus(result.state);
77
+ };
78
+ result.addEventListener("change", changeHandler);
79
+ }).catch((err) => {
80
+ if (cancelled) return;
81
+ setState(null);
82
+ setStatus("error");
83
+ setError(err instanceof Error ? err : new Error(String(err)));
84
+ });
85
+ return () => {
86
+ cancelled = true;
87
+ if (permissionStatus && changeHandler) {
88
+ permissionStatus.removeEventListener("change", changeHandler);
89
+ }
90
+ };
91
+ }, [serialized]);
92
+ return (0, import_react.useMemo)(
93
+ () => ({ state, status, isSupported, error }),
94
+ [state, status, isSupported, error]
95
+ );
96
+ }
97
+ // Annotate the CommonJS export names for ESM import in node:
98
+ 0 && (module.exports = {
99
+ isPermissionsSupported,
100
+ serializeDescriptor,
101
+ usePermission
102
+ });
103
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/usePermission.ts","../src/utils.ts"],"sourcesContent":["export { usePermission } from \"./usePermission\";\nexport { isPermissionsSupported, serializeDescriptor } from \"./utils\";\nexport type {\n UsePermissionStatus,\n UsePermissionDescriptor,\n UsePermissionReturn,\n} from \"./types\";\n","import { useEffect, useMemo, useRef, useState } from \"react\";\nimport type {\n UsePermissionDescriptor,\n UsePermissionReturn,\n UsePermissionStatus,\n} from \"./types\";\nimport { isPermissionsSupported, serializeDescriptor } from \"./utils\";\n\n/**\n * Track the status of a Permissions API permission, with live updates.\n *\n * Calls `navigator.permissions.query(descriptor)` and subscribes to the returned\n * `PermissionStatus`'s `change` event, so the returned `state` reflects the user\n * granting or revoking the permission without a re-mount. The hook is SSR-safe\n * (reports `unsupported` on the server) and StrictMode/concurrent-safe (the async\n * query is race-guarded and the listener is cleaned up on unmount).\n *\n * **Descriptor identity:** the effect is keyed on a *serialized* copy of the\n * descriptor's fields (`name`, `userVisibleOnly`, `sysex`, …), not on the object\n * reference. So passing an inline literal — `usePermission({ name: 'camera' })` —\n * does not re-query on every render; it re-queries only when the descriptor's\n * contents actually change. No `useMemo` on the caller's side is required.\n *\n * @param descriptor - The permission to query, e.g. `{ name: 'camera' }`,\n * `{ name: 'geolocation' }`, `{ name: 'push', userVisibleOnly: true }`,\n * `{ name: 'midi', sysex: true }`. Accepts any permission name (the standard\n * ones plus browser-specific ones like `'camera'`/`'microphone'`).\n * @returns `{ state, status, isSupported, error }` — see {@link UsePermissionReturn}.\n *\n * @example\n * ```tsx\n * function CameraBadge() {\n * const { state, status } = usePermission({ name: \"camera\" });\n *\n * // Branch on `status` (deterministic \"idle\" on the first render, both on the\n * // server and client) so the UI is hydration-safe.\n * if (status === \"idle\" || status === \"pending\") return <span>Checking…</span>;\n * if (status === \"unsupported\") return <span>Permissions API unavailable</span>;\n * if (status === \"error\") return <span>Could not read camera permission</span>;\n *\n * return <span>Camera: {state}</span>; // 'granted' | 'denied' | 'prompt'\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Live updates: the badge re-renders when the user changes the permission in\n * // the browser UI — no polling, no re-mount.\n * function MicIndicator() {\n * const { state } = usePermission({ name: \"microphone\" });\n * return <div data-granted={state === \"granted\"}>Mic: {state ?? \"unknown\"}</div>;\n * }\n * ```\n */\nexport function usePermission(\n descriptor: UsePermissionDescriptor\n): UsePermissionReturn {\n // Every returned field starts deterministic (identical on server and the\n // first client render) so the hook never causes a hydration mismatch; the\n // effect corrects them after mount. In particular `isSupported` is NOT\n // computed in render — that would be `false` on the server and `true` on the\n // client's first paint, mismatching any UI that branches on it.\n const [state, setState] = useState<PermissionState | null>(null);\n const [status, setStatus] = useState<UsePermissionStatus>(\"idle\");\n const [error, setError] = useState<Error | null>(null);\n const [isSupported, setIsSupported] = useState(false);\n\n // Read the latest descriptor from a ref inside the effect. The effect is keyed\n // on the serialized descriptor (a primitive), so it re-runs on content changes\n // while `descriptorRef` guarantees the query uses the current fields.\n const descriptorRef = useRef(descriptor);\n descriptorRef.current = descriptor;\n\n const serialized = serializeDescriptor(descriptor);\n\n useEffect(() => {\n const supported = isPermissionsSupported();\n setIsSupported(supported);\n if (!supported) {\n setState(null);\n setStatus(\"unsupported\");\n setError(null);\n return;\n }\n\n let cancelled = false;\n let permissionStatus: PermissionStatus | null = null;\n let changeHandler: (() => void) | null = null;\n\n // Clear any prior permission's value so consumers don't read stale `state`\n // while the new query for a changed descriptor is in flight.\n setState(null);\n setStatus(\"pending\");\n setError(null);\n\n navigator.permissions\n .query(descriptorRef.current as PermissionDescriptor)\n .then((result) => {\n if (cancelled) return;\n permissionStatus = result;\n setState(result.state);\n setStatus(result.state);\n\n changeHandler = () => {\n // Read the fresh value off the live PermissionStatus object.\n setState(result.state);\n setStatus(result.state);\n };\n result.addEventListener(\"change\", changeHandler);\n })\n .catch((err: unknown) => {\n if (cancelled) return;\n setState(null);\n setStatus(\"error\");\n setError(err instanceof Error ? err : new Error(String(err)));\n });\n\n return () => {\n cancelled = true;\n if (permissionStatus && changeHandler) {\n permissionStatus.removeEventListener(\"change\", changeHandler);\n }\n };\n // `serialized` is the stable primitive key; `descriptorRef`/setters are stable.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [serialized]);\n\n return useMemo<UsePermissionReturn>(\n () => ({ state, status, isSupported, error }),\n [state, status, isSupported, error]\n );\n}\n","import type { UsePermissionDescriptor } from \"./types\";\n\n/**\n * Whether the Permissions API (`navigator.permissions.query`) is available in the\n * current environment. Returns `false` during SSR (no `navigator`) and in browsers\n * that do not implement the Permissions API.\n *\n * @example\n * ```ts\n * if (isPermissionsSupported()) {\n * // safe to call navigator.permissions.query(...)\n * }\n * ```\n */\nexport function isPermissionsSupported(): boolean {\n return (\n typeof navigator !== \"undefined\" &&\n typeof navigator.permissions !== \"undefined\" &&\n typeof navigator.permissions.query === \"function\"\n );\n}\n\n/**\n * Produce a stable string key from a permission descriptor's meaningful fields.\n *\n * `usePermission` keys its effect on this string, not on the descriptor object's\n * reference — so an inline `usePermission({ name: 'camera' })` (a fresh object\n * literal every render) does **not** re-query on every render. It re-queries only\n * when the descriptor's actual contents change. Keys are sorted so field order in\n * the literal does not affect the result.\n *\n * @example\n * ```ts\n * serializeDescriptor({ name: \"midi\", sysex: true }); // '{\"name\":\"midi\",\"sysex\":true}'\n * serializeDescriptor({ sysex: true, name: \"midi\" }); // same string\n * ```\n */\nexport function serializeDescriptor(descriptor: UsePermissionDescriptor): string {\n const keys = Object.keys(descriptor).sort();\n const normalized: Record<string, unknown> = {};\n for (const key of keys) {\n normalized[key] = descriptor[key];\n }\n return JSON.stringify(normalized);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAqD;;;ACc9C,SAAS,yBAAkC;AAChD,SACE,OAAO,cAAc,eACrB,OAAO,UAAU,gBAAgB,eACjC,OAAO,UAAU,YAAY,UAAU;AAE3C;AAiBO,SAAS,oBAAoB,YAA6C;AAC/E,QAAM,OAAO,OAAO,KAAK,UAAU,EAAE,KAAK;AAC1C,QAAM,aAAsC,CAAC;AAC7C,aAAW,OAAO,MAAM;AACtB,eAAW,GAAG,IAAI,WAAW,GAAG;AAAA,EAClC;AACA,SAAO,KAAK,UAAU,UAAU;AAClC;;;ADUO,SAAS,cACd,YACqB;AAMrB,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAiC,IAAI;AAC/D,QAAM,CAAC,QAAQ,SAAS,QAAI,uBAA8B,MAAM;AAChE,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAuB,IAAI;AACrD,QAAM,CAAC,aAAa,cAAc,QAAI,uBAAS,KAAK;AAKpD,QAAM,oBAAgB,qBAAO,UAAU;AACvC,gBAAc,UAAU;AAExB,QAAM,aAAa,oBAAoB,UAAU;AAEjD,8BAAU,MAAM;AACd,UAAM,YAAY,uBAAuB;AACzC,mBAAe,SAAS;AACxB,QAAI,CAAC,WAAW;AACd,eAAS,IAAI;AACb,gBAAU,aAAa;AACvB,eAAS,IAAI;AACb;AAAA,IACF;AAEA,QAAI,YAAY;AAChB,QAAI,mBAA4C;AAChD,QAAI,gBAAqC;AAIzC,aAAS,IAAI;AACb,cAAU,SAAS;AACnB,aAAS,IAAI;AAEb,cAAU,YACP,MAAM,cAAc,OAA+B,EACnD,KAAK,CAAC,WAAW;AAChB,UAAI,UAAW;AACf,yBAAmB;AACnB,eAAS,OAAO,KAAK;AACrB,gBAAU,OAAO,KAAK;AAEtB,sBAAgB,MAAM;AAEpB,iBAAS,OAAO,KAAK;AACrB,kBAAU,OAAO,KAAK;AAAA,MACxB;AACA,aAAO,iBAAiB,UAAU,aAAa;AAAA,IACjD,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,UAAI,UAAW;AACf,eAAS,IAAI;AACb,gBAAU,OAAO;AACjB,eAAS,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AAAA,IAC9D,CAAC;AAEH,WAAO,MAAM;AACX,kBAAY;AACZ,UAAI,oBAAoB,eAAe;AACrC,yBAAiB,oBAAoB,UAAU,aAAa;AAAA,MAC9D;AAAA,IACF;AAAA,EAGF,GAAG,CAAC,UAAU,CAAC;AAEf,aAAO;AAAA,IACL,OAAO,EAAE,OAAO,QAAQ,aAAa,MAAM;AAAA,IAC3C,CAAC,OAAO,QAAQ,aAAa,KAAK;AAAA,EACpC;AACF;","names":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,74 @@
1
+ // src/usePermission.ts
2
+ import { useEffect, useMemo, useRef, useState } from "react";
3
+
4
+ // src/utils.ts
5
+ function isPermissionsSupported() {
6
+ return typeof navigator !== "undefined" && typeof navigator.permissions !== "undefined" && typeof navigator.permissions.query === "function";
7
+ }
8
+ function serializeDescriptor(descriptor) {
9
+ const keys = Object.keys(descriptor).sort();
10
+ const normalized = {};
11
+ for (const key of keys) {
12
+ normalized[key] = descriptor[key];
13
+ }
14
+ return JSON.stringify(normalized);
15
+ }
16
+
17
+ // src/usePermission.ts
18
+ function usePermission(descriptor) {
19
+ const [state, setState] = useState(null);
20
+ const [status, setStatus] = useState("idle");
21
+ const [error, setError] = useState(null);
22
+ const [isSupported, setIsSupported] = useState(false);
23
+ const descriptorRef = useRef(descriptor);
24
+ descriptorRef.current = descriptor;
25
+ const serialized = serializeDescriptor(descriptor);
26
+ useEffect(() => {
27
+ const supported = isPermissionsSupported();
28
+ setIsSupported(supported);
29
+ if (!supported) {
30
+ setState(null);
31
+ setStatus("unsupported");
32
+ setError(null);
33
+ return;
34
+ }
35
+ let cancelled = false;
36
+ let permissionStatus = null;
37
+ let changeHandler = null;
38
+ setState(null);
39
+ setStatus("pending");
40
+ setError(null);
41
+ navigator.permissions.query(descriptorRef.current).then((result) => {
42
+ if (cancelled) return;
43
+ permissionStatus = result;
44
+ setState(result.state);
45
+ setStatus(result.state);
46
+ changeHandler = () => {
47
+ setState(result.state);
48
+ setStatus(result.state);
49
+ };
50
+ result.addEventListener("change", changeHandler);
51
+ }).catch((err) => {
52
+ if (cancelled) return;
53
+ setState(null);
54
+ setStatus("error");
55
+ setError(err instanceof Error ? err : new Error(String(err)));
56
+ });
57
+ return () => {
58
+ cancelled = true;
59
+ if (permissionStatus && changeHandler) {
60
+ permissionStatus.removeEventListener("change", changeHandler);
61
+ }
62
+ };
63
+ }, [serialized]);
64
+ return useMemo(
65
+ () => ({ state, status, isSupported, error }),
66
+ [state, status, isSupported, error]
67
+ );
68
+ }
69
+ export {
70
+ isPermissionsSupported,
71
+ serializeDescriptor,
72
+ usePermission
73
+ };
74
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/usePermission.ts","../src/utils.ts"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from \"react\";\nimport type {\n UsePermissionDescriptor,\n UsePermissionReturn,\n UsePermissionStatus,\n} from \"./types\";\nimport { isPermissionsSupported, serializeDescriptor } from \"./utils\";\n\n/**\n * Track the status of a Permissions API permission, with live updates.\n *\n * Calls `navigator.permissions.query(descriptor)` and subscribes to the returned\n * `PermissionStatus`'s `change` event, so the returned `state` reflects the user\n * granting or revoking the permission without a re-mount. The hook is SSR-safe\n * (reports `unsupported` on the server) and StrictMode/concurrent-safe (the async\n * query is race-guarded and the listener is cleaned up on unmount).\n *\n * **Descriptor identity:** the effect is keyed on a *serialized* copy of the\n * descriptor's fields (`name`, `userVisibleOnly`, `sysex`, …), not on the object\n * reference. So passing an inline literal — `usePermission({ name: 'camera' })` —\n * does not re-query on every render; it re-queries only when the descriptor's\n * contents actually change. No `useMemo` on the caller's side is required.\n *\n * @param descriptor - The permission to query, e.g. `{ name: 'camera' }`,\n * `{ name: 'geolocation' }`, `{ name: 'push', userVisibleOnly: true }`,\n * `{ name: 'midi', sysex: true }`. Accepts any permission name (the standard\n * ones plus browser-specific ones like `'camera'`/`'microphone'`).\n * @returns `{ state, status, isSupported, error }` — see {@link UsePermissionReturn}.\n *\n * @example\n * ```tsx\n * function CameraBadge() {\n * const { state, status } = usePermission({ name: \"camera\" });\n *\n * // Branch on `status` (deterministic \"idle\" on the first render, both on the\n * // server and client) so the UI is hydration-safe.\n * if (status === \"idle\" || status === \"pending\") return <span>Checking…</span>;\n * if (status === \"unsupported\") return <span>Permissions API unavailable</span>;\n * if (status === \"error\") return <span>Could not read camera permission</span>;\n *\n * return <span>Camera: {state}</span>; // 'granted' | 'denied' | 'prompt'\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Live updates: the badge re-renders when the user changes the permission in\n * // the browser UI — no polling, no re-mount.\n * function MicIndicator() {\n * const { state } = usePermission({ name: \"microphone\" });\n * return <div data-granted={state === \"granted\"}>Mic: {state ?? \"unknown\"}</div>;\n * }\n * ```\n */\nexport function usePermission(\n descriptor: UsePermissionDescriptor\n): UsePermissionReturn {\n // Every returned field starts deterministic (identical on server and the\n // first client render) so the hook never causes a hydration mismatch; the\n // effect corrects them after mount. In particular `isSupported` is NOT\n // computed in render — that would be `false` on the server and `true` on the\n // client's first paint, mismatching any UI that branches on it.\n const [state, setState] = useState<PermissionState | null>(null);\n const [status, setStatus] = useState<UsePermissionStatus>(\"idle\");\n const [error, setError] = useState<Error | null>(null);\n const [isSupported, setIsSupported] = useState(false);\n\n // Read the latest descriptor from a ref inside the effect. The effect is keyed\n // on the serialized descriptor (a primitive), so it re-runs on content changes\n // while `descriptorRef` guarantees the query uses the current fields.\n const descriptorRef = useRef(descriptor);\n descriptorRef.current = descriptor;\n\n const serialized = serializeDescriptor(descriptor);\n\n useEffect(() => {\n const supported = isPermissionsSupported();\n setIsSupported(supported);\n if (!supported) {\n setState(null);\n setStatus(\"unsupported\");\n setError(null);\n return;\n }\n\n let cancelled = false;\n let permissionStatus: PermissionStatus | null = null;\n let changeHandler: (() => void) | null = null;\n\n // Clear any prior permission's value so consumers don't read stale `state`\n // while the new query for a changed descriptor is in flight.\n setState(null);\n setStatus(\"pending\");\n setError(null);\n\n navigator.permissions\n .query(descriptorRef.current as PermissionDescriptor)\n .then((result) => {\n if (cancelled) return;\n permissionStatus = result;\n setState(result.state);\n setStatus(result.state);\n\n changeHandler = () => {\n // Read the fresh value off the live PermissionStatus object.\n setState(result.state);\n setStatus(result.state);\n };\n result.addEventListener(\"change\", changeHandler);\n })\n .catch((err: unknown) => {\n if (cancelled) return;\n setState(null);\n setStatus(\"error\");\n setError(err instanceof Error ? err : new Error(String(err)));\n });\n\n return () => {\n cancelled = true;\n if (permissionStatus && changeHandler) {\n permissionStatus.removeEventListener(\"change\", changeHandler);\n }\n };\n // `serialized` is the stable primitive key; `descriptorRef`/setters are stable.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [serialized]);\n\n return useMemo<UsePermissionReturn>(\n () => ({ state, status, isSupported, error }),\n [state, status, isSupported, error]\n );\n}\n","import type { UsePermissionDescriptor } from \"./types\";\n\n/**\n * Whether the Permissions API (`navigator.permissions.query`) is available in the\n * current environment. Returns `false` during SSR (no `navigator`) and in browsers\n * that do not implement the Permissions API.\n *\n * @example\n * ```ts\n * if (isPermissionsSupported()) {\n * // safe to call navigator.permissions.query(...)\n * }\n * ```\n */\nexport function isPermissionsSupported(): boolean {\n return (\n typeof navigator !== \"undefined\" &&\n typeof navigator.permissions !== \"undefined\" &&\n typeof navigator.permissions.query === \"function\"\n );\n}\n\n/**\n * Produce a stable string key from a permission descriptor's meaningful fields.\n *\n * `usePermission` keys its effect on this string, not on the descriptor object's\n * reference — so an inline `usePermission({ name: 'camera' })` (a fresh object\n * literal every render) does **not** re-query on every render. It re-queries only\n * when the descriptor's actual contents change. Keys are sorted so field order in\n * the literal does not affect the result.\n *\n * @example\n * ```ts\n * serializeDescriptor({ name: \"midi\", sysex: true }); // '{\"name\":\"midi\",\"sysex\":true}'\n * serializeDescriptor({ sysex: true, name: \"midi\" }); // same string\n * ```\n */\nexport function serializeDescriptor(descriptor: UsePermissionDescriptor): string {\n const keys = Object.keys(descriptor).sort();\n const normalized: Record<string, unknown> = {};\n for (const key of keys) {\n normalized[key] = descriptor[key];\n }\n return JSON.stringify(normalized);\n}\n"],"mappings":";AAAA,SAAS,WAAW,SAAS,QAAQ,gBAAgB;;;ACc9C,SAAS,yBAAkC;AAChD,SACE,OAAO,cAAc,eACrB,OAAO,UAAU,gBAAgB,eACjC,OAAO,UAAU,YAAY,UAAU;AAE3C;AAiBO,SAAS,oBAAoB,YAA6C;AAC/E,QAAM,OAAO,OAAO,KAAK,UAAU,EAAE,KAAK;AAC1C,QAAM,aAAsC,CAAC;AAC7C,aAAW,OAAO,MAAM;AACtB,eAAW,GAAG,IAAI,WAAW,GAAG;AAAA,EAClC;AACA,SAAO,KAAK,UAAU,UAAU;AAClC;;;ADUO,SAAS,cACd,YACqB;AAMrB,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAiC,IAAI;AAC/D,QAAM,CAAC,QAAQ,SAAS,IAAI,SAA8B,MAAM;AAChE,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AACrD,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,KAAK;AAKpD,QAAM,gBAAgB,OAAO,UAAU;AACvC,gBAAc,UAAU;AAExB,QAAM,aAAa,oBAAoB,UAAU;AAEjD,YAAU,MAAM;AACd,UAAM,YAAY,uBAAuB;AACzC,mBAAe,SAAS;AACxB,QAAI,CAAC,WAAW;AACd,eAAS,IAAI;AACb,gBAAU,aAAa;AACvB,eAAS,IAAI;AACb;AAAA,IACF;AAEA,QAAI,YAAY;AAChB,QAAI,mBAA4C;AAChD,QAAI,gBAAqC;AAIzC,aAAS,IAAI;AACb,cAAU,SAAS;AACnB,aAAS,IAAI;AAEb,cAAU,YACP,MAAM,cAAc,OAA+B,EACnD,KAAK,CAAC,WAAW;AAChB,UAAI,UAAW;AACf,yBAAmB;AACnB,eAAS,OAAO,KAAK;AACrB,gBAAU,OAAO,KAAK;AAEtB,sBAAgB,MAAM;AAEpB,iBAAS,OAAO,KAAK;AACrB,kBAAU,OAAO,KAAK;AAAA,MACxB;AACA,aAAO,iBAAiB,UAAU,aAAa;AAAA,IACjD,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,UAAI,UAAW;AACf,eAAS,IAAI;AACb,gBAAU,OAAO;AACjB,eAAS,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AAAA,IAC9D,CAAC;AAEH,WAAO,MAAM;AACX,kBAAY;AACZ,UAAI,oBAAoB,eAAe;AACrC,yBAAiB,oBAAoB,UAAU,aAAa;AAAA,MAC9D;AAAA,IACF;AAAA,EAGF,GAAG,CAAC,UAAU,CAAC;AAEf,SAAO;AAAA,IACL,OAAO,EAAE,OAAO,QAAQ,aAAa,MAAM;AAAA,IAC3C,CAAC,OAAO,QAAQ,aAAa,KAAK;AAAA,EACpC;AACF;","names":[]}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@usefy/use-permission",
3
+ "version": "0.20.0",
4
+ "description": "A React hook for reading Permissions API status with live updates, SSR-safe and typed",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "sideEffects": false,
19
+ "peerDependencies": {
20
+ "react": "^18.0.0 || ^19.0.0"
21
+ },
22
+ "devDependencies": {
23
+ "@testing-library/jest-dom": "^6.9.1",
24
+ "@testing-library/react": "^16.3.1",
25
+ "@types/react": "^19.0.0",
26
+ "@types/react-dom": "^19.0.0",
27
+ "jsdom": "^27.3.0",
28
+ "react": "^19.0.0",
29
+ "react-dom": "^19.0.0",
30
+ "rimraf": "^6.0.1",
31
+ "tsup": "^8.0.0",
32
+ "typescript": "^5.0.0",
33
+ "vitest": "^4.0.16"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/mirunamu00/usefy.git",
41
+ "directory": "packages/hooks/use-permission"
42
+ },
43
+ "license": "MIT",
44
+ "keywords": [
45
+ "react",
46
+ "hooks",
47
+ "permissions",
48
+ "permission",
49
+ "permissions-api",
50
+ "navigator",
51
+ "usePermission"
52
+ ],
53
+ "scripts": {
54
+ "build": "tsup",
55
+ "dev": "tsup --watch",
56
+ "test": "vitest run",
57
+ "test:watch": "vitest",
58
+ "typecheck": "tsc --noEmit",
59
+ "clean": "rimraf dist"
60
+ }
61
+ }