@casl/react 6.0.0 → 7.0.0-rc

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 CHANGED
@@ -4,7 +4,11 @@
4
4
  [![](https://img.shields.io/npm/dm/%40casl%2Freact.svg)](https://www.npmjs.com/package/%40casl%2Freact)
5
5
  [![Support](https://img.shields.io/badge/Support-github%20discussions-green?style=flat&link=https://github.com/stalniy/casl/discussions)](https://github.com/stalniy/casl/discussions)
6
6
 
7
- This package allows to integrate `@casl/ability` with [React] application. It provides `Can` component that allow to hide or show UI elements based on user ability to see them.
7
+ This package integrates `@casl/ability` with [React]. It provides:
8
+
9
+ * `AbilityProvider` to expose the current `Ability` instance through React context
10
+ * declarative `<Can>` component for conditional rendering
11
+ * `useAbility` hook for imperative checks that stay in sync with ability updates
8
12
 
9
13
  > `@casl/react` perfectly works with [React Native](https://reactnative.dev/)
10
14
 
@@ -18,8 +22,42 @@ yarn add @casl/react @casl/ability
18
22
  pnpm add @casl/react @casl/ability
19
23
  ```
20
24
 
25
+ ## Quick Start
26
+
27
+ ```tsx
28
+ import { createMongoAbility } from '@casl/ability';
29
+ import { AbilityProvider, Can, useAbility } from '@casl/react';
30
+
31
+ const ability = createMongoAbility([
32
+ { action: 'read', subject: 'Post' },
33
+ { action: 'create', subject: 'Post' },
34
+ ]);
35
+
36
+ export function App() {
37
+ return (
38
+ <AbilityProvider value={ability}>
39
+ <Can I="read" a="Post">
40
+ <div>List of posts</div>
41
+ </Can>
42
+ <CreatePostButton />
43
+ </AbilityProvider>
44
+ );
45
+ }
46
+
47
+ function CreatePostButton() {
48
+ const ability = useAbility();
49
+ return ability.can('create', 'Post') && (
50
+ <button>Create Post</button>
51
+ );
52
+ }
53
+ ```
54
+
55
+ Use `<Can>` for straightforward conditional rendering in JSX and `useAbility()` when the permission check is part of more complex component logic.
56
+
21
57
  ## Can component
22
58
 
59
+ `<Can>` reads the current `Ability` instance from `AbilityProvider`, re-renders when rules change, and memoizes the relevant rule lookup until the ability, rules, or relevant props change.
60
+
23
61
  It accepts children and 6 properties:
24
62
 
25
63
  * `do` - name of the action (e.g., `read`, `update`). Has an alias `I`
@@ -45,71 +83,48 @@ It accepts children and 6 properties:
45
83
  ```jsx
46
84
  export default () => (
47
85
  <Can I="create" a="Post" passThrough>
48
- {allowed => <button disabled={!allowed}>Save</button>}
86
+ {({ isAllowed, reason }) => (
87
+ <button disabled={!isAllowed} title={reason}>Save</button>
88
+ )}
49
89
  </Can>
50
90
  )
51
91
  ```
52
92
 
53
- * `ability` - an instance of `Ability` which will be used to check permissions
54
- * `children` - elements to hide or render. May be either a render function:
93
+ * `children` - elements to hide or render. May be either a render function that receives `{ isAllowed, ability, reason }`:
55
94
 
56
95
  ```jsx
57
- export default () => <Can I="create" a="Post" ability={ability}>
58
- {() => <button onClick={this.createPost}>Create Post</button>}
96
+ export default () => <Can I="create" a="Post">
97
+ {({ isAllowed, reason }) => (
98
+ <button disabled={!isAllowed} title={reason}>Create Post</button>
99
+ )}
59
100
  </Can>
60
101
  ```
61
102
 
62
103
  or React elements:
63
104
 
64
105
  ```jsx
65
- export default () => <Can I="create" a="Post" ability={ability}>
106
+ export default () => <Can I="create" a="Post">
66
107
  <button onClick={this.createPost}>Create Post</button>
67
108
  </Can>
68
109
  ```
69
110
 
70
111
  > it's better to pass children as a render function because it will not create additional React elements if user doesn't have ability to do some action (in the case above `create Post`)
71
112
 
72
- Don't be scared by the amount of properties component takes, we will talk about how to bind some of them.
73
-
74
- ### Bind Can to a particular Ability instance
75
-
76
- It'd be inconvenient to pass `ability` in every `Can` component. That's why there are 2 function which allow to bind `Can` to use a particular instance of `Ability`:
113
+ For simple visibility guards, `<Can>` keeps JSX readable. For more complex conditions, composing several checks, or passing authorization state deeper into your component tree, prefer `useAbility`.
77
114
 
78
- * `createCanBoundTo`\
79
- This function was created to support version of React < 16.4.0, those versions doesn't have [Context API][react-ctx-api]. Can be used like this:
115
+ ### Provide Ability instance
80
116
 
81
- ```js @{data-filename="Can.js"}
82
- import { createCanBoundTo } from '@casl/react';
83
- import ability from './ability';
84
-
85
- export const Can = createCanBoundTo(ability);
86
- ```
87
- * `createContextualCan`\
88
- This function is created to support [React's Context API][react-ctx-api] and can be used like this:
89
-
90
- ```js @{data-filename="Can.js"}
91
- import { createContext } from 'react';
92
- import { createContextualCan } from '@casl/react';
93
-
94
- export const AbilityContext = createContext();
95
- export const Can = createContextualCan(AbilityContext.Consumer);
96
- ```
97
-
98
- The 2 methods are almost the same, the 2nd one is slightly better because it will allow you to provide different `Ability` instances to different parts of your app and inject ability using [`contextType` static property](https://reactjs.org/docs/context.html#classcontexttype). Choose your way based on the version of React you use.
99
-
100
- > In this guide, we will use `createContextualCan` as it covers more cases in modern React development.
101
-
102
- To finalize things, we need to provide an instance of `Ability` via `AbilityContext.Provider`:
117
+ Wrap the part of your app that needs authorization checks with `AbilityProvider`:
103
118
 
104
119
  ```jsx @{data-filename="App.jsx"}
105
- import { AbilityContext } from './Can'
106
- import ability from './ability'
120
+ import { AbilityProvider } from '@casl/react';
121
+ import ability from './ability';
107
122
 
108
- export default function App({ props }) {
123
+ export default function App() {
109
124
  return (
110
- <AbilityContext.Provider value={ability}>
125
+ <AbilityProvider ability={ability}>
111
126
  <TodoApp />
112
- </AbilityContext.Provider>
127
+ </AbilityProvider>
113
128
  )
114
129
  }
115
130
  ```
@@ -120,7 +135,7 @@ and use our `Can` component:
120
135
 
121
136
  ```jsx
122
137
  import React, { Component } from 'react'
123
- import { Can } from './Can'
138
+ import { Can } from '@casl/react'
124
139
 
125
140
  export class TodoApp extends Component {
126
141
  createTodo = () => {
@@ -139,60 +154,14 @@ export class TodoApp extends Component {
139
154
 
140
155
  ### Imperative access to Ability instance
141
156
 
142
- Sometimes the logic in a component may be a bit complicated, so you can't use `<Can>` component. In such cases, you can use [React's `contextType` component property](https://reactjs.org/docs/context.html#classcontexttype):
143
-
144
- ```jsx
145
- import React, { Component } from 'react'
146
- import { AbilityContext } from './Can'
147
-
148
- export class TodoApp extends Component {
149
- createTodo = () => {
150
- // logic to show new todo form
151
- };
152
-
153
- render() {
154
- return (
155
- <div>
156
- {this.context.can('create', 'Todo') &&
157
- <button onClick={this.createTodo}>Create Todo</button>}
158
- </div>
159
- );
160
- }
161
- }
162
-
163
- TodoApp.contextType = AbilityContext;
164
- ```
165
-
166
- or `useContext` hook:
167
-
168
- ```jsx
169
- import React, { useContext } from 'react';
170
- import { AbilityContext } from './Can'
171
-
172
- export default () => {
173
- const createTodo = () => { /* logic to show new todo form */ };
174
- const ability = useContext(AbilityContext);
175
-
176
- return (
177
- <div>
178
- {ability.can('create', 'Todo') &&
179
- <button onClick={createTodo}>Create Todo</button>}
180
- </div>
181
- );
182
- }
183
- ```
184
-
185
- In that case, you need to create a new `Ability` instance when you want to update user permissions (don't use `update` method, it won't trigger re-rendering in this case) or you need to force re-render the whole app.
186
-
187
- To make things easier, `@casl/react` provides `useAbility` hook that accepts `React.Context` as the only argument (the same as `useContext`), but triggers re-render in the component where you use this hook when you update `Ability` rules. The example above can be rewritten to:
157
+ Sometimes the logic in a component is more complex than a simple visibility guard. In such cases, use `useAbility`. It reads the current ability from `AbilityProvider` and re-renders the component when ability rules change:
188
158
 
189
159
  ```jsx
190
160
  import { useAbility } from '@casl/react';
191
- import { AbilityContext } from './Can'
192
161
 
193
162
  export default () => {
194
163
  const createTodo = () => { /* logic to show new todo form */ };
195
- const ability = useAbility(AbilityContext);
164
+ const ability = useAbility();
196
165
 
197
166
  return (
198
167
  <div>
@@ -203,28 +172,6 @@ export default () => {
203
172
  }
204
173
  ```
205
174
 
206
- ### Usage note on React < 16.4 with TypeScript
207
-
208
- If you use TypeScript and React < 16.4 make sure to create a file `contextAPIPatch.d.ts` file with the next content:
209
-
210
- ```ts
211
- declare module 'react' {
212
- export type Consumer<T> = any;
213
- }
214
- ```
215
-
216
- and include it in your `tscofig.json`, otherwise your app won't compile:
217
-
218
- ```json
219
- {
220
- // other configuration options
221
- "include": [
222
- "src/**/*",
223
- "./contextAPIPatch.d.ts" // <-- add this line
224
- ]
225
- }
226
- ```
227
-
228
175
  ### Property names and aliases
229
176
 
230
177
  As you can see from the code above, component name and its property names and values create an English sentence, actually a question. For example, the code below reads as `Can I create a Post`:
@@ -270,10 +217,10 @@ Majority of applications that need permission checking support have something li
270
217
 
271
218
  Let's imagine that server returns user with a role on login:
272
219
 
273
- ```ts @{data-filename="Login.jsx"}
220
+ ```ts @{data-filename="Login.tsx"}
274
221
  import { AbilityBuilder, Ability } from '@casl/ability';
275
- import React, { useState, useContext } from 'react';
276
- import { AbilityContext } from './Can';
222
+ import React, { useState } from 'react';
223
+ import { useAbility } from '@casl/react';
277
224
 
278
225
  function updateAbility(ability, user) {
279
226
  const { can, rules } = new AbilityBuilder(Ability);
@@ -290,7 +237,7 @@ function updateAbility(ability, user) {
290
237
  export default () => {
291
238
  const [username, setUsername] = useState('');
292
239
  const [password, setPassword] = useState('');
293
- const ability = useContext(AbilityContext);
240
+ const ability = useAbility();
294
241
  const login = () => {
295
242
  const params = {
296
243
  method: 'POST',
@@ -314,7 +261,7 @@ export default () => {
314
261
 
315
262
  ## `useAbility` usage within hooks
316
263
 
317
- Using the return value `ability` of `const ability = useAbility(AbilityContext)` within a hook dependencies won't trigger a rerender when the rules are updated. You have to specify `ability.rules`:
264
+ Using the return value `ability` of `const ability = useAbility()` within hook dependencies won't trigger a rerender when the rules are updated. You have to specify `ability.rules`:
318
265
 
319
266
  ```jsx
320
267
  const posts = React.useMemo(() => getPosts(ability), [ability.rules]);
@@ -335,4 +282,3 @@ If you'd like to help us sustain our community and project, consider [to become
335
282
 
336
283
  [contributing]: https://github.com/stalniy/casl/blob/master/CONTRIBUTING.md
337
284
  [React]: https://reactjs.org/
338
- [react-ctx-api]: https://reactjs.org/docs/context.html
@@ -0,0 +1,49 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, {
2
+ value: "Module"
3
+ });
4
+
5
+ var e = Object.create, t = Object.defineProperty, r = Object.getOwnPropertyDescriptor, o = Object.getOwnPropertyNames, n = Object.getPrototypeOf, l = Object.prototype.hasOwnProperty;
6
+
7
+ let u = require("react");
8
+
9
+ var c, i, a;
10
+
11
+ i = 1, a = null != (c = u) ? e(n(c)) : {}, u = ((e, n, u, c) => {
12
+ if (n && "object" == typeof n || "function" == typeof n) for (var i, a = o(n), b = 0, s = a.length; b < s; b++) i = a[b],
13
+ l.call(e, i) || i === u || t(e, i, {
14
+ get: (e => n[e]).bind(null, i),
15
+ enumerable: !(c = r(n, i)) || c.enumerable
16
+ });
17
+ return e;
18
+ })(!i && c && c.t ? a : t(a, "default", {
19
+ value: c,
20
+ enumerable: !0
21
+ }), c);
22
+
23
+ const b = (0, u.createContext)(null), s = [], f = () => {};
24
+
25
+ function p() {
26
+ const e = (0, u.useContext)(b), t = (0, u.useCallback)(t => e?.on("updated", t) || f, [ e ]), r = (0,
27
+ u.useCallback)(() => e?.rules || s, [ e ]);
28
+ if ((0, u.useSyncExternalStore)(t, r, r), !e) throw new Error("AbilityContext is not provided. Please make sure to wrap your component tree with <AbilityProvider>.");
29
+ return e;
30
+ }
31
+
32
+ const d = u.default.memo(function(e) {
33
+ const t = p(), r = e, o = r.of || r.a || r.an || r.this || r.on, n = r.I || r.do, l = r.field, c = u.default.useMemo(() => t.relevantRuleFor(n, o, l), [ t, t.rules, n, o, l ]);
34
+ let i = !!c && !c.inverted;
35
+ e.not && (i = !i);
36
+ const a = "function" == typeof e.children ? e.children({
37
+ isAllowed: i,
38
+ ability: t,
39
+ reason: c?.reason
40
+ }) : e.children;
41
+ return e.passThrough || i ? a : null;
42
+ });
43
+
44
+ exports.AbilityProvider = function({children: e, value: t}) {
45
+ return (0, u.createElement)(b.Provider, {
46
+ value: t
47
+ }, e);
48
+ }, exports.Can = d, exports.useAbility = p;
49
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["React"],"sources":["../../src/hooks/useAbility.ts","../../src/Can.ts"],"sourcesContent":["import { createElement, useContext, useCallback, useSyncExternalStore, createContext } from 'react';\nimport type { AnyAbility } from '@casl/ability';\n\nconst AbilityContext = createContext<AnyAbility | null>(null);\nconst EMPTY_RULES: any[] = [];\nconst noop = () => {};\n\nexport function AbilityProvider<T extends AnyAbility>({\n children,\n value,\n}: {\n children: React.ReactNode;\n value: T;\n}) {\n return createElement(AbilityContext.Provider, { value }, children);\n}\n\nexport function useAbility<T extends AnyAbility>(): T {\n const ability = useContext(AbilityContext);\n const subscribe = useCallback(\n (callback: () => void) => ability?.on('updated', callback) || noop,\n [ability],\n );\n const getSnapshot = useCallback(() => ability?.rules || EMPTY_RULES, [ability]);\n\n useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n if (!ability) {\n throw new Error('AbilityContext is not provided. Please make sure to wrap your component tree with <AbilityProvider>.');\n }\n\n return ability as T;\n}\n","import React, { ReactNode } from 'react';\nimport {\n AbilityTuple,\n SubjectType,\n AnyAbility,\n Generics,\n Abilities,\n IfString,\n} from '@casl/ability';\nimport { useAbility } from './hooks/useAbility';\n\ntype AbilityCanProps<\n T extends Abilities,\n Else = IfString<T, { do: T } | { I: T }>\n> = T extends AbilityTuple\n ? { do: T[0], on: T[1], field?: string } |\n { I: T[0], a: Extract<T[1], SubjectType>, field?: string } |\n { I: T[0], an: Extract<T[1], SubjectType>, field?: string } |\n { I: T[0], this: Exclude<T[1], SubjectType>, field?: string }\n : Else;\n\ninterface ExtraProps {\n not?: boolean\n passThrough?: boolean\n}\n\ninterface CanExtraProps<T extends AnyAbility> extends ExtraProps {\n children: ReactNode | ((exposes: {\n isAllowed: boolean;\n ability: T;\n reason: string | undefined;\n }) => ReactNode)\n}\n\nexport type CanProps<T extends AnyAbility> =\n AbilityCanProps<Generics<T>['abilities']> & CanExtraProps<T>;\n\nfunction CanComponent<T extends AnyAbility>(props: CanProps<T>) {\n const ability = useAbility();\n const propsWithAliases = props as CanProps<T> & {\n of?: unknown\n a?: unknown\n an?: unknown\n this?: unknown\n on?: unknown\n I?: unknown\n do?: unknown\n field?: string\n };\n const subject =\n propsWithAliases.of ||\n propsWithAliases.a ||\n propsWithAliases.an ||\n propsWithAliases.this ||\n propsWithAliases.on;\n const action = propsWithAliases.I || propsWithAliases.do;\n const field = propsWithAliases.field;\n const rule = React.useMemo(() => {\n return ability.relevantRuleFor(action, subject, field);\n }, [ability, ability.rules, action, subject, field]);\n let isAllowed = !!rule && !rule.inverted;\n if (props.not) isAllowed = !isAllowed;\n\n const elements = typeof props.children === 'function'\n ? props.children({ isAllowed, ability: ability as T, reason: rule?.reason })\n : props.children;\n\n return props.passThrough || isAllowed ? elements as ReactNode : null;\n}\n\nexport const Can = React.memo(CanComponent) as typeof CanComponent;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAGA,MAAM,KAAA,GAAA,EAAA,eAAkD,OAClD,IAAqB,IACrB,IAAA;;AAYN,SAAgB;IACd,MAAM,KAAA,GAAA,EAAA,YAAqB,IACrB,KAAA,GAAA,EAAA,aACH,KAAyB,GAAS,GAAG,WAAW,MAAa,GAC9D,EAAC,MAEG,KAAA;IAAA,EAAA,aAAA,MAAgC,GAAS,SAAS,GAAa,EAAC;IAItE,KAFA,GAAA,EAAA,sBAAqB,GAAW,GAAa,KAExC,GACH,MAAM,IAAI,MAAM;IAGlB,OAAO;;;ACuCT,MAAa,IAAMA,EAAAA,QAAM,KAjCzB,SAA4C;IAC1C,MAAM,IAAU,KACV,IAAmB,GAUnB,IACJ,EAAiB,MACjB,EAAiB,KACjB,EAAiB,MACjB,EAAiB,QACjB,EAAiB,IACb,IAAS,EAAiB,KAAK,EAAiB,IAChD,IAAQ,EAAiB,OACzB,IAAOA,EAAAA,QAAM,QAAA,MACV,EAAQ,gBAAgB,GAAQ,GAAS,IAC/C,EAAC,GAAS,EAAQ,OAAO,GAAQ,GAAS;IAC7C,IAAI,MAAc,MAAS,EAAK;IAC5B,EAAM,QAAK,KAAa;IAE5B,MAAM,IAAqC,qBAAnB,EAAM,WAC1B,EAAM,SAAS;QAAE;QAAoB;QAAc,QAAQ,GAAM;SACjE,EAAM;IAEV,OAAO,EAAM,eAAe,IAAY,IAAwB;;;0BD5DlE,UAAsD,UACpD,GAAA,OACA;IAKA,QAAA,GAAA,EAAA,eAAqB,EAAe,UAAU;QAAE;OAAS"}
@@ -0,0 +1,30 @@
1
+ import t, { createContext as n, createElement as o, useCallback as e, useContext as r, useSyncExternalStore as i } from "react";
2
+
3
+ const l = n(null), u = [], c = () => {};
4
+
5
+ function s({children: t, value: n}) {
6
+ return o(l.Provider, {
7
+ value: n
8
+ }, t);
9
+ }
10
+
11
+ function a() {
12
+ const t = r(l), n = e(n => t?.on("updated", n) || c, [ t ]), o = e(() => t?.rules || u, [ t ]);
13
+ if (i(n, o, o), !t) throw new Error("AbilityContext is not provided. Please make sure to wrap your component tree with <AbilityProvider>.");
14
+ return t;
15
+ }
16
+
17
+ const d = t.memo(function(n) {
18
+ const o = a(), e = n, r = e.of || e.a || e.an || e.this || e.on, i = e.I || e.do, l = e.field, u = t.useMemo(() => o.relevantRuleFor(i, r, l), [ o, o.rules, i, r, l ]);
19
+ let c = !!u && !u.inverted;
20
+ n.not && (c = !c);
21
+ const s = "function" == typeof n.children ? n.children({
22
+ isAllowed: c,
23
+ ability: o,
24
+ reason: u?.reason
25
+ }) : n.children;
26
+ return n.passThrough || c ? s : null;
27
+ });
28
+
29
+ export { s as AbilityProvider, d as Can, a as useAbility };
30
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/hooks/useAbility.ts","../../src/Can.ts"],"sourcesContent":["import { createElement, useContext, useCallback, useSyncExternalStore, createContext } from 'react';\nimport type { AnyAbility } from '@casl/ability';\n\nconst AbilityContext = createContext<AnyAbility | null>(null);\nconst EMPTY_RULES: any[] = [];\nconst noop = () => {};\n\nexport function AbilityProvider<T extends AnyAbility>({\n children,\n value,\n}: {\n children: React.ReactNode;\n value: T;\n}) {\n return createElement(AbilityContext.Provider, { value }, children);\n}\n\nexport function useAbility<T extends AnyAbility>(): T {\n const ability = useContext(AbilityContext);\n const subscribe = useCallback(\n (callback: () => void) => ability?.on('updated', callback) || noop,\n [ability],\n );\n const getSnapshot = useCallback(() => ability?.rules || EMPTY_RULES, [ability]);\n\n useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n if (!ability) {\n throw new Error('AbilityContext is not provided. Please make sure to wrap your component tree with <AbilityProvider>.');\n }\n\n return ability as T;\n}\n","import React, { ReactNode } from 'react';\nimport {\n AbilityTuple,\n SubjectType,\n AnyAbility,\n Generics,\n Abilities,\n IfString,\n} from '@casl/ability';\nimport { useAbility } from './hooks/useAbility';\n\ntype AbilityCanProps<\n T extends Abilities,\n Else = IfString<T, { do: T } | { I: T }>\n> = T extends AbilityTuple\n ? { do: T[0], on: T[1], field?: string } |\n { I: T[0], a: Extract<T[1], SubjectType>, field?: string } |\n { I: T[0], an: Extract<T[1], SubjectType>, field?: string } |\n { I: T[0], this: Exclude<T[1], SubjectType>, field?: string }\n : Else;\n\ninterface ExtraProps {\n not?: boolean\n passThrough?: boolean\n}\n\ninterface CanExtraProps<T extends AnyAbility> extends ExtraProps {\n children: ReactNode | ((exposes: {\n isAllowed: boolean;\n ability: T;\n reason: string | undefined;\n }) => ReactNode)\n}\n\nexport type CanProps<T extends AnyAbility> =\n AbilityCanProps<Generics<T>['abilities']> & CanExtraProps<T>;\n\nfunction CanComponent<T extends AnyAbility>(props: CanProps<T>) {\n const ability = useAbility();\n const propsWithAliases = props as CanProps<T> & {\n of?: unknown\n a?: unknown\n an?: unknown\n this?: unknown\n on?: unknown\n I?: unknown\n do?: unknown\n field?: string\n };\n const subject =\n propsWithAliases.of ||\n propsWithAliases.a ||\n propsWithAliases.an ||\n propsWithAliases.this ||\n propsWithAliases.on;\n const action = propsWithAliases.I || propsWithAliases.do;\n const field = propsWithAliases.field;\n const rule = React.useMemo(() => {\n return ability.relevantRuleFor(action, subject, field);\n }, [ability, ability.rules, action, subject, field]);\n let isAllowed = !!rule && !rule.inverted;\n if (props.not) isAllowed = !isAllowed;\n\n const elements = typeof props.children === 'function'\n ? props.children({ isAllowed, ability: ability as T, reason: rule?.reason })\n : props.children;\n\n return props.passThrough || isAllowed ? elements as ReactNode : null;\n}\n\nexport const Can = React.memo(CanComponent) as typeof CanComponent;\n"],"mappings":";;AAGA,MAAM,IAAiB,EAAiC,OAClD,IAAqB,IACrB,IAAA;;AAEN,SAAgB,GAAsC,UACpD,GAAA,OACA;IAKA,OAAO,EAAc,EAAe,UAAU;QAAE;OAAS;;;AAG3D,SAAgB;IACd,MAAM,IAAU,EAAW,IACrB,IAAY,EACf,KAAyB,GAAS,GAAG,WAAW,MAAa,GAC9D,EAAC,MAEG,IAAc,EAAA,MAAkB,GAAS,SAAS,GAAa,EAAC;IAItE,IAFA,EAAqB,GAAW,GAAa,KAExC,GACH,MAAM,IAAI,MAAM;IAGlB,OAAO;;;ACuCT,MAAa,IAAM,EAAM,KAjCzB,SAA4C;IAC1C,MAAM,IAAU,KACV,IAAmB,GAUnB,IACJ,EAAiB,MACjB,EAAiB,KACjB,EAAiB,MACjB,EAAiB,QACjB,EAAiB,IACb,IAAS,EAAiB,KAAK,EAAiB,IAChD,IAAQ,EAAiB,OACzB,IAAO,EAAM,QAAA,MACV,EAAQ,gBAAgB,GAAQ,GAAS,IAC/C,EAAC,GAAS,EAAQ,OAAO,GAAQ,GAAS;IAC7C,IAAI,MAAc,MAAS,EAAK;IAC5B,EAAM,QAAK,KAAa;IAE5B,MAAM,IAAqC,qBAAnB,EAAM,WAC1B,EAAM,SAAS;QAAE;QAAoB;QAAc,QAAQ,GAAM;SACjE,EAAM;IAEV,OAAO,EAAM,eAAe,IAAY,IAAwB"}
@@ -1,4 +1,4 @@
1
- import { PureComponent, ReactNode } from 'react';
1
+ import React, { ReactNode } from 'react';
2
2
  import { AbilityTuple, SubjectType, AnyAbility, Generics, Abilities, IfString } from '@casl/ability';
3
3
  type AbilityCanProps<T extends Abilities, Else = IfString<T, {
4
4
  do: T;
@@ -26,29 +26,13 @@ interface ExtraProps {
26
26
  passThrough?: boolean;
27
27
  }
28
28
  interface CanExtraProps<T extends AnyAbility> extends ExtraProps {
29
- ability: T;
30
- children: ReactNode | ((isAllowed: boolean, ability: T) => ReactNode);
31
- }
32
- interface BoundCanExtraProps<T extends AnyAbility> extends ExtraProps {
33
- ability?: T;
34
- children: ReactNode | ((isAllowed: boolean, ability: T) => ReactNode);
29
+ children: ReactNode | ((exposes: {
30
+ isAllowed: boolean;
31
+ ability: T;
32
+ reason: string | undefined;
33
+ }) => ReactNode);
35
34
  }
36
35
  export type CanProps<T extends AnyAbility> = AbilityCanProps<Generics<T>['abilities']> & CanExtraProps<T>;
37
- export type BoundCanProps<T extends AnyAbility> = AbilityCanProps<Generics<T>['abilities']> & BoundCanExtraProps<T>;
38
- export declare class Can<T extends AnyAbility> extends PureComponent<CanProps<T>, {
39
- t: boolean;
40
- }> {
41
- private _isAllowed;
42
- private _ability;
43
- private _unsubscribeFromAbility;
44
- state: {
45
- t: boolean;
46
- };
47
- componentWillUnmount(): void;
48
- private _connectToAbility;
49
- get allowed(): boolean;
50
- private _canRender;
51
- render(): ReactNode;
52
- private _renderChildren;
53
- }
36
+ declare function CanComponent<T extends AnyAbility>(props: CanProps<T>): React.ReactNode;
37
+ export declare const Can: typeof CanComponent;
54
38
  export {};
@@ -1,3 +1,6 @@
1
- import React from 'react';
2
- import { AnyAbility } from '@casl/ability';
3
- export declare function useAbility<T extends AnyAbility>(context: React.Context<T>): T;
1
+ import type { AnyAbility } from '@casl/ability';
2
+ export declare function AbilityProvider<T extends AnyAbility>({ children, value, }: {
3
+ children: React.ReactNode;
4
+ value: T;
5
+ }): import("react").FunctionComponentElement<import("react").ProviderProps<AnyAbility | null>>;
6
+ export declare function useAbility<T extends AnyAbility>(): T;
@@ -1,3 +1,2 @@
1
1
  export * from './Can';
2
- export * from './factory';
3
2
  export * from './hooks/useAbility';
package/package.json CHANGED
@@ -1,16 +1,14 @@
1
1
  {
2
2
  "name": "@casl/react",
3
- "version": "6.0.0",
3
+ "version": "7.0.0-rc",
4
4
  "description": "React component for CASL which makes it easy to add permissions in any React application",
5
- "main": "dist/umd/index.js",
6
- "module": "dist/es5m/index.js",
7
- "es2015": "dist/es6m/index.mjs",
8
- "typings": "./index.d.ts",
5
+ "main": "./dist/esm/index.mjs",
6
+ "type": "module",
9
7
  "exports": {
10
8
  ".": {
11
9
  "types": "./dist/types/index.d.ts",
12
- "import": "./dist/es6m/index.mjs",
13
- "require": "./dist/umd/index.js"
10
+ "require": "./dist/cjs/index.cjs",
11
+ "import": "./dist/esm/index.mjs"
14
12
  }
15
13
  },
16
14
  "repository": {
@@ -32,11 +30,10 @@
32
30
  "author": "Sergii Stotskyi <sergiy.stotskiy@gmail.com>",
33
31
  "license": "MIT",
34
32
  "peerDependencies": {
35
- "@casl/ability": "^4.0.0 || ^5.1.0 || ^6.0.0",
33
+ "@casl/ability": "^4.0.0 || ^5.1.0 || ^6.0.0 || ^7.0.0",
36
34
  "react": "^18.0.0 || ^19.0.0"
37
35
  },
38
36
  "devDependencies": {
39
- "@casl/ability": "^6.0.0",
40
37
  "@casl/dx": "^1.0.0",
41
38
  "@testing-library/dom": "^10.4.0",
42
39
  "@testing-library/react": "^16.1.0",
@@ -46,19 +43,18 @@
46
43
  "chai": "^4.1.0",
47
44
  "chai-spies": "^1.0.0",
48
45
  "react": "^19.0.0",
49
- "react-dom": "^19.0.0"
46
+ "react-dom": "^19.0.0",
47
+ "@casl/ability": "7.0.0-rc"
50
48
  },
51
49
  "files": [
52
- "dist",
53
- "*.d.ts"
50
+ "dist"
54
51
  ],
55
52
  "scripts": {
56
- "build.prepare": "rm -rf dist/* && npm run build.types",
57
- "build": "npm run build.prepare && BUILD_TYPES=es5m,es6m,umd dx rollup -n casl.react -g react:React,prop-types:React.PropTypes,@casl/ability:casl",
58
- "build.types": "dx tsc",
53
+ "build.prepare": "rm -rf dist/* && dx tsc",
54
+ "build": "npm run build.prepare && dx tsdown",
59
55
  "lint": "dx eslint src/ spec/",
60
56
  "test": "dx jest --env jsdom",
61
57
  "release.prepare": "npm run lint && npm test && NODE_ENV=production npm run build",
62
- "release": "npm run release.prepare && pnpm publish"
58
+ "release": "npm run release.prepare && pnpm publish --tag \"$RELEASE_DIST_TAG\" --publish-branch \"$RELEASE_PUBLISH_BRANCH\""
63
59
  }
64
60
  }
@@ -1,2 +0,0 @@
1
- import t,{PureComponent as r,createElement as n}from"react";function i(t,r){for(var n=0;n<r.length;n++){var i=r[n];i.enumerable=i.enumerable||false,i.configurable=true,"value"in i&&(i.writable=true),Object.defineProperty(t,a(i.key),i)}}function u(t,r,n){return r&&i(t.prototype,r),Object.defineProperty(t,"prototype",{writable:false}),t}function e(){return e=Object.assign?Object.assign.bind():function(t){for(var r=1;r<arguments.length;r++){var n=arguments[r];for(var i in n)({}).hasOwnProperty.call(n,i)&&(t[i]=n[i])}return t},e.apply(null,arguments)}function o(t,r){t.prototype=Object.create(r.prototype),t.prototype.constructor=t,f(t,r)}function f(t,r){return f=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,r){return t.__proto__=r,t},f(t,r)}function c(t,r){if("object"!=typeof t||!t)return t;var n=t[Symbol.toPrimitive];if(void 0!==n){var i=n.call(t,r);if("object"!=typeof i)return i;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(t)}function a(t){var r=c(t,"string");return"symbol"==typeof r?r:r+""}var s=function t(){};var v=function(t){function r(){var r;for(var n=arguments.length,i=new Array(n),u=0;u<n;u++)i[u]=arguments[u];r=t.call.apply(t,[this].concat(i))||this;r.i=false;r.u=null;r.o=s;r.state={t:true};return r}o(r,t);var n=r.prototype;n.componentWillUnmount=function t(){this.o()};n.v=function _connectToAbility(t){var r=this;if(t===this.u)return;this.o();this.u=null;if(t){this.u=t;this.o=t.on("updated",function(){return r.setState({t:!r.state.t})})}};n.l=function t(){var r=this.props;var n=r.of||r.a||r.an||r.this||r.on;var i=r.not?"cannot":"can";return r.ability[i](r.I||r.do,n,r.field)};n.render=function t(){this.v(this.props.ability);this.i=this.l();return this.props.passThrough||this.i?this.h():null};n.h=function t(){var r=this.props,n=r.children,i=r.ability;var u=typeof n==="function"?n(this.i,i):n;return u};return u(r,[{key:"allowed",get:function t(){return this.i}}])}(r);function l(t){return function(r){return n(t,{children:function t(i){return n(v,e({},r,{ability:r.ability||i}))}})}}function useAbility(r){var n=t.useContext(r);var i=t.useCallback(function(t){return n.on("updated",t)},[n]);var u=t.useCallback(function(){return n.rules},[n]);t.useSyncExternalStore(i,u,u);return n}export{v as Can,l as createContextualCan,useAbility};
2
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sources":["../../src/Can.ts","../../src/factory.ts","../../src/hooks/useAbility.ts"],"sourcesContent":["import { PureComponent, ReactNode } from 'react';\nimport {\n Unsubscribe,\n AbilityTuple,\n SubjectType,\n AnyAbility,\n Generics,\n Abilities,\n IfString,\n} from '@casl/ability';\n\nconst noop = () => {};\n\ntype AbilityCanProps<\n T extends Abilities,\n Else = IfString<T, { do: T } | { I: T }>\n> = T extends AbilityTuple\n ? { do: T[0], on: T[1], field?: string } |\n { I: T[0], a: Extract<T[1], SubjectType>, field?: string } |\n { I: T[0], an: Extract<T[1], SubjectType>, field?: string } |\n { I: T[0], this: Exclude<T[1], SubjectType>, field?: string }\n : Else;\n\ninterface ExtraProps {\n not?: boolean\n passThrough?: boolean\n}\n\ninterface CanExtraProps<T extends AnyAbility> extends ExtraProps {\n ability: T\n children: ReactNode | ((isAllowed: boolean, ability: T) => ReactNode)\n}\n\ninterface BoundCanExtraProps<T extends AnyAbility> extends ExtraProps {\n ability?: T\n children: ReactNode | ((isAllowed: boolean, ability: T) => ReactNode)\n}\n\nexport type CanProps<T extends AnyAbility> =\n AbilityCanProps<Generics<T>['abilities']> & CanExtraProps<T>;\nexport type BoundCanProps<T extends AnyAbility> =\n AbilityCanProps<Generics<T>['abilities']> & BoundCanExtraProps<T>;\n\nexport class Can<T extends AnyAbility> extends PureComponent<CanProps<T>, { t: boolean }> {\n private _isAllowed = false;\n private _ability: T | null = null;\n private _unsubscribeFromAbility: Unsubscribe = noop;\n state = { t: true }\n\n componentWillUnmount() {\n this._unsubscribeFromAbility();\n }\n\n private _connectToAbility(ability?: T) {\n if (ability === this._ability) {\n return;\n }\n\n this._unsubscribeFromAbility();\n this._ability = null;\n\n if (ability) {\n this._ability = ability;\n this._unsubscribeFromAbility = ability.on('updated', () => this.setState({ t: !this.state.t }));\n }\n }\n\n get allowed() {\n return this._isAllowed;\n }\n\n private _canRender(): boolean {\n const props: any = this.props;\n const subject = props.of || props.a || props.an || props.this || props.on;\n const can = props.not ? 'cannot' : 'can';\n\n return props.ability[can](props.I || props.do, subject, props.field);\n }\n\n render() {\n this._connectToAbility(this.props.ability);\n this._isAllowed = this._canRender();\n return this.props.passThrough || this._isAllowed ? this._renderChildren() : null;\n }\n\n private _renderChildren() {\n const { children, ability } = this.props;\n const elements = typeof children === 'function'\n ? children(this._isAllowed, ability as any)\n : children;\n\n return elements as ReactNode;\n }\n}\n","import { AnyAbility } from '@casl/ability';\nimport { Consumer, FunctionComponent, createElement } from 'react';\nimport { BoundCanProps, Can } from './Can';\n\nexport function createContextualCan<T extends AnyAbility>(\n Getter: Consumer<T>\n): FunctionComponent<BoundCanProps<T>> {\n return (props: BoundCanProps<T>) => createElement(Getter, {\n children: (ability: T) => \n createElement(Can, { ...props, ability: props.ability || ability } as any),\n });\n}\n","import React from 'react';\nimport { AnyAbility } from '@casl/ability';\n\nexport function useAbility<T extends AnyAbility>(context: React.Context<T>): T {\n const ability = React.useContext<T>(context);\n\n const subscribe = React.useCallback(\n (callback: () => void) => ability.on('updated', callback),\n [ability],\n );\n\n const getSnapshot = React.useCallback(() => ability.rules, [ability]);\n\n React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n return ability;\n}\n"],"names":["noop","Can","_PureComponent","_this","_len","arguments","length","args","Array","_key","call","apply","this","concat","_isAllowed","_ability","_unsubscribeFromAbility","state","t","_inheritsLoose","_proto","prototype","componentWillUnmount","_connectToAbility","ability","_this2","on","setState","_canRender","props","subject","of","a","an","can","not","I","do","field","render","passThrough","_renderChildren","_this$props","children","elements","_createClass","key","get","PureComponent","createContextualCan","Getter","createElement","_extends","useAbility","context","React","useContext","subscribe","useCallback","callback","getSnapshot","rules","useSyncExternalStore"],"mappings":"giCAWA,IAAMA,EAAO,SAAPA,IAAc,EAgCpB,IAAaC,WAAGC,GAAA,SAAAD,IAAA,IAAAE,EAAA,IAAA,IAAAC,EAAAC,UAAAC,OAAAC,EAAA,IAAAC,MAAAJ,GAAAK,EAAA,EAAAA,EAAAL,EAAAK,IAAAF,EAAAE,GAAAJ,UAAAI,GAAAN,EAAAD,EAAAQ,KAAAC,MAAAT,EAAA,CAAAU,MAAAC,OAAAN,KAAAK,KAAAT,EACNW,EAAa,MAAKX,EAClBY,EAAqB,KAAIZ,EACzBa,EAAuChB,EAAIG,EACnDc,MAAQ,CAAEC,EAAG,MAAM,OAAAf,CAAA,CAAAgB,EAAAlB,EAAAC,GAAA,IAAAkB,EAAAnB,EAAAoB,UAAAD,EAEnBE,qBAAA,SAAAA,IACEV,KAAKI,GACP,EAACI,EAEOG,EAAR,SAAQA,kBAAkBC,GAAa,IAAAC,EAAAb,KACrC,GAAIY,IAAYZ,KAAKG,EACnB,OAGFH,KAAKI,IACLJ,KAAKG,EAAW,KAEhB,GAAIS,EAAS,CACXZ,KAAKG,EAAWS,EAChBZ,KAAKI,EAA0BQ,EAAQE,GAAG,UAAW,WAAA,OAAMD,EAAKE,SAAS,CAAET,GAAIO,EAAKR,MAAMC,GAAI,EAChG,CACF,EAACE,EAMOQ,EAAR,SAAQA,IACN,IAAMC,EAAajB,KAAKiB,MACxB,IAAMC,EAAUD,EAAME,IAAMF,EAAMG,GAAKH,EAAMI,IAAMJ,EAAMjB,MAAQiB,EAAMH,GACvE,IAAMQ,EAAML,EAAMM,IAAM,SAAW,MAEnC,OAAON,EAAML,QAAQU,GAAKL,EAAMO,GAAKP,EAAMQ,GAAIP,EAASD,EAAMS,MAChE,EAAClB,EAEDmB,OAAA,SAAAA,IACE3B,KAAKW,EAAkBX,KAAKiB,MAAML,SAClCZ,KAAKE,EAAaF,KAAKgB,IACvB,OAAOhB,KAAKiB,MAAMW,aAAe5B,KAAKE,EAAaF,KAAK6B,IAAoB,IAC9E,EAACrB,EAEOqB,EAAR,SAAQA,IACN,IAAAC,EAA8B9B,KAAKiB,MAA3Bc,EAAQD,EAARC,SAAUnB,EAAOkB,EAAPlB,QAClB,IAAMoB,SAAkBD,IAAa,WACjCA,EAAS/B,KAAKE,EAAYU,GAC1BmB,EAEJ,OAAOC,CACT,EAAC,OAAAC,EAAA5C,EAAA,CAAA,CAAA6C,IAAA,UAAAC,IAzBD,SAAAA,IACE,OAAOnC,KAAKE,CACd,IAAC,EA1B4CkC,GCvCxC,SAASC,EACdC,GAEA,OAAO,SAACrB,GAAuB,OAAKsB,EAAcD,EAAQ,CACxDP,SAAU,SAAVA,EAAWnB,GAAU,OACnB2B,EAAclD,EAAGmD,KAAOvB,EAAK,CAAEL,QAASK,EAAML,SAAWA,IAAiB,GAC5E,CACJ,CCRO,SAAS6B,WAAiCC,GAC/C,IAAM9B,EAAU+B,EAAMC,WAAcF,GAEpC,IAAMG,EAAYF,EAAMG,YACtB,SAACC,GAAoB,OAAKnC,EAAQE,GAAG,UAAWiC,EAAS,EACzD,CAACnC,IAGH,IAAMoC,EAAcL,EAAMG,YAAY,WAAA,OAAMlC,EAAQqC,KAAK,EAAE,CAACrC,IAE5D+B,EAAMO,qBAAqBL,EAAWG,EAAaA,GAEnD,OAAOpC,CACT"}
@@ -1,2 +0,0 @@
1
- import t,{PureComponent as i,createElement as n}from"react";const s=()=>{};class e extends i{constructor(...t){super(...t);this.i=false;this.h=null;this.o=s;this.state={t:true}}componentWillUnmount(){this.o()}u(t){if(t===this.h)return;this.o();this.h=null;if(t){this.h=t;this.o=t.on("updated",()=>this.setState({t:!this.state.t}))}}get allowed(){return this.i}l(){const t=this.props;const i=t.of||t.a||t.an||t.this||t.on;const n=t.not?"cannot":"can";return t.ability[n](t.I||t.do,i,t.field)}render(){this.u(this.props.ability);this.i=this.l();return this.props.passThrough||this.i?this.p():null}p(){const{children:t,ability:i}=this.props;const n=typeof t==="function"?t(this.i,i):t;return n}}function r(t){return i=>n(t,{children:t=>n(e,Object.assign({},i,{ability:i.ability||t}))})}function useAbility(i){const n=t.useContext(i);const s=t.useCallback(t=>n.on("updated",t),[n]);const e=t.useCallback(()=>n.rules,[n]);t.useSyncExternalStore(s,e,e);return n}export{e as Can,r as createContextualCan,useAbility};
2
- //# sourceMappingURL=index.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","sources":["../../src/Can.ts","../../src/factory.ts","../../src/hooks/useAbility.ts"],"sourcesContent":["import { PureComponent, ReactNode } from 'react';\nimport {\n Unsubscribe,\n AbilityTuple,\n SubjectType,\n AnyAbility,\n Generics,\n Abilities,\n IfString,\n} from '@casl/ability';\n\nconst noop = () => {};\n\ntype AbilityCanProps<\n T extends Abilities,\n Else = IfString<T, { do: T } | { I: T }>\n> = T extends AbilityTuple\n ? { do: T[0], on: T[1], field?: string } |\n { I: T[0], a: Extract<T[1], SubjectType>, field?: string } |\n { I: T[0], an: Extract<T[1], SubjectType>, field?: string } |\n { I: T[0], this: Exclude<T[1], SubjectType>, field?: string }\n : Else;\n\ninterface ExtraProps {\n not?: boolean\n passThrough?: boolean\n}\n\ninterface CanExtraProps<T extends AnyAbility> extends ExtraProps {\n ability: T\n children: ReactNode | ((isAllowed: boolean, ability: T) => ReactNode)\n}\n\ninterface BoundCanExtraProps<T extends AnyAbility> extends ExtraProps {\n ability?: T\n children: ReactNode | ((isAllowed: boolean, ability: T) => ReactNode)\n}\n\nexport type CanProps<T extends AnyAbility> =\n AbilityCanProps<Generics<T>['abilities']> & CanExtraProps<T>;\nexport type BoundCanProps<T extends AnyAbility> =\n AbilityCanProps<Generics<T>['abilities']> & BoundCanExtraProps<T>;\n\nexport class Can<T extends AnyAbility> extends PureComponent<CanProps<T>, { t: boolean }> {\n private _isAllowed = false;\n private _ability: T | null = null;\n private _unsubscribeFromAbility: Unsubscribe = noop;\n state = { t: true }\n\n componentWillUnmount() {\n this._unsubscribeFromAbility();\n }\n\n private _connectToAbility(ability?: T) {\n if (ability === this._ability) {\n return;\n }\n\n this._unsubscribeFromAbility();\n this._ability = null;\n\n if (ability) {\n this._ability = ability;\n this._unsubscribeFromAbility = ability.on('updated', () => this.setState({ t: !this.state.t }));\n }\n }\n\n get allowed() {\n return this._isAllowed;\n }\n\n private _canRender(): boolean {\n const props: any = this.props;\n const subject = props.of || props.a || props.an || props.this || props.on;\n const can = props.not ? 'cannot' : 'can';\n\n return props.ability[can](props.I || props.do, subject, props.field);\n }\n\n render() {\n this._connectToAbility(this.props.ability);\n this._isAllowed = this._canRender();\n return this.props.passThrough || this._isAllowed ? this._renderChildren() : null;\n }\n\n private _renderChildren() {\n const { children, ability } = this.props;\n const elements = typeof children === 'function'\n ? children(this._isAllowed, ability as any)\n : children;\n\n return elements as ReactNode;\n }\n}\n","import { AnyAbility } from '@casl/ability';\nimport { Consumer, FunctionComponent, createElement } from 'react';\nimport { BoundCanProps, Can } from './Can';\n\nexport function createContextualCan<T extends AnyAbility>(\n Getter: Consumer<T>\n): FunctionComponent<BoundCanProps<T>> {\n return (props: BoundCanProps<T>) => createElement(Getter, {\n children: (ability: T) => \n createElement(Can, { ...props, ability: props.ability || ability } as any),\n });\n}\n","import React from 'react';\nimport { AnyAbility } from '@casl/ability';\n\nexport function useAbility<T extends AnyAbility>(context: React.Context<T>): T {\n const ability = React.useContext<T>(context);\n\n const subscribe = React.useCallback(\n (callback: () => void) => ability.on('updated', callback),\n [ability],\n );\n\n const getSnapshot = React.useCallback(() => ability.rules, [ability]);\n\n React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n return ability;\n}\n"],"names":["noop","Can","PureComponent","constructor","args","super","this","_isAllowed","_ability","_unsubscribeFromAbility","state","t","componentWillUnmount","_connectToAbility","ability","on","setState","allowed","_canRender","props","subject","of","a","an","can","not","I","do","field","render","passThrough","_renderChildren","children","elements","createContextualCan","Getter","createElement","Object","assign","useAbility","context","React","useContext","subscribe","useCallback","callback","getSnapshot","rules","useSyncExternalStore"],"mappings":"4DAWA,MAAMA,EAAOA,OAgCN,MAAMC,UAAkCC,EAA2CC,WAAAA,IAAAC,GAAAC,SAAAD,GAAAE,KAChFC,EAAa,MAAKD,KAClBE,EAAqB,KAAIF,KACzBG,EAAuCT,EAAIM,KACnDI,MAAQ,CAAEC,EAAG,KAAM,CAEnBC,oBAAAA,GACEN,KAAKG,GACP,CAEQI,CAAAA,CAAkBC,GACxB,GAAIA,IAAYR,KAAKE,EACnB,OAGFF,KAAKG,IACLH,KAAKE,EAAW,KAEhB,GAAIM,EAAS,CACXR,KAAKE,EAAWM,EAChBR,KAAKG,EAA0BK,EAAQC,GAAG,UAAW,IAAMT,KAAKU,SAAS,CAAEL,GAAIL,KAAKI,MAAMC,IAC5F,CACF,CAEA,WAAIM,GACF,OAAOX,KAAKC,CACd,CAEQW,CAAAA,GACN,MAAMC,EAAab,KAAKa,MACxB,MAAMC,EAAUD,EAAME,IAAMF,EAAMG,GAAKH,EAAMI,IAAMJ,EAAMb,MAAQa,EAAMJ,GACvE,MAAMS,EAAML,EAAMM,IAAM,SAAW,MAEnC,OAAON,EAAML,QAAQU,GAAKL,EAAMO,GAAKP,EAAMQ,GAAIP,EAASD,EAAMS,MAChE,CAEAC,MAAAA,GACEvB,KAAKO,EAAkBP,KAAKa,MAAML,SAClCR,KAAKC,EAAaD,KAAKY,IACvB,OAAOZ,KAAKa,MAAMW,aAAexB,KAAKC,EAAaD,KAAKyB,IAAoB,IAC9E,CAEQA,CAAAA,GACN,MAAMC,SAAEA,EAAQlB,QAAEA,GAAYR,KAAKa,MACnC,MAAMc,SAAkBD,IAAa,WACjCA,EAAS1B,KAAKC,EAAYO,GAC1BkB,EAEJ,OAAOC,CACT,ECxFK,SAASC,EACdC,GAEA,OAAQhB,GAA4BiB,EAAcD,EAAQ,CACxDH,SAAWlB,GACTsB,EAAcnC,EAAGoC,OAAAC,OAAA,CAAA,EAAOnB,EAAK,CAAEL,QAASK,EAAML,SAAWA,MAE/D,CCRO,SAASyB,WAAiCC,GAC/C,MAAM1B,EAAU2B,EAAMC,WAAcF,GAEpC,MAAMG,EAAYF,EAAMG,YACrBC,GAAyB/B,EAAQC,GAAG,UAAW8B,GAChD,CAAC/B,IAGH,MAAMgC,EAAcL,EAAMG,YAAY,IAAM9B,EAAQiC,MAAO,CAACjC,IAE5D2B,EAAMO,qBAAqBL,EAAWG,EAAaA,GAEnD,OAAOhC,CACT"}
@@ -1,4 +0,0 @@
1
- import { AnyAbility } from '@casl/ability';
2
- import { Consumer, FunctionComponent } from 'react';
3
- import { BoundCanProps } from './Can';
4
- export declare function createContextualCan<T extends AnyAbility>(Getter: Consumer<T>): FunctionComponent<BoundCanProps<T>>;
package/dist/umd/index.js DELETED
@@ -1,2 +0,0 @@
1
- (function(t,n){typeof exports==="object"&&typeof module!=="undefined"?n(exports,require("react")):typeof define==="function"&&define.amd?define(["exports","react"],n):(t=typeof globalThis!=="undefined"?globalThis:t||self,n((t.casl=t.casl||{},t.casl.react={}),t.React))})(this,function(t,n){"use strict";function r(t,n){for(var r=0;r<n.length;r++){var e=n[r];e.enumerable=e.enumerable||false,e.configurable=true,"value"in e&&(e.writable=true),Object.defineProperty(t,c(e.key),e)}}function e(t,n,e){return n&&r(t.prototype,n),Object.defineProperty(t,"prototype",{writable:false}),t}function i(){return i=Object.assign?Object.assign.bind():function(t){for(var n=1;n<arguments.length;n++){var r=arguments[n];for(var e in r)({}).hasOwnProperty.call(r,e)&&(t[e]=r[e])}return t},i.apply(null,arguments)}function u(t,n){t.prototype=Object.create(n.prototype),t.prototype.constructor=t,o(t,n)}function o(t,n){return o=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,n){return t.__proto__=n,t},o(t,n)}function f(t,n){if("object"!=typeof t||!t)return t;var r=t[Symbol.toPrimitive];if(void 0!==r){var e=r.call(t,n);if("object"!=typeof e)return e;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(t)}function c(t){var n=f(t,"string");return"symbol"==typeof n?n:n+""}var a=function t(){};var s=function(t){function n(){var n;for(var r=arguments.length,e=new Array(r),i=0;i<r;i++)e[i]=arguments[i];n=t.call.apply(t,[this].concat(e))||this;n.i=false;n.u=null;n.o=a;n.state={t:true};return n}u(n,t);var r=n.prototype;r.componentWillUnmount=function t(){this.o()};r.l=function _connectToAbility(t){var n=this;if(t===this.u)return;this.o();this.u=null;if(t){this.u=t;this.o=t.on("updated",function(){return n.setState({t:!n.state.t})})}};r.v=function t(){var n=this.props;var r=n.of||n.a||n.an||n.this||n.on;var e=n.not?"cannot":"can";return n.ability[e](n.I||n.do,r,n.field)};r.render=function t(){this.l(this.props.ability);this.i=this.v();return this.props.passThrough||this.i?this.h():null};r.h=function t(){var n=this.props,r=n.children,e=n.ability;var i=typeof r==="function"?r(this.i,e):r;return i};return e(n,[{key:"allowed",get:function t(){return this.i}}])}(n.PureComponent);function l(t){return function(r){return n.createElement(t,{children:function t(e){return n.createElement(s,i({},r,{ability:r.ability||e}))}})}}function useAbility(t){var r=n.useContext(t);var e=n.useCallback(function(t){return r.on("updated",t)},[r]);var i=n.useCallback(function(){return r.rules},[r]);n.useSyncExternalStore(e,i,i);return r}t.Can=s;t.createContextualCan=l;t.useAbility=useAbility});
2
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sources":["../../src/Can.ts","../../src/factory.ts","../../src/hooks/useAbility.ts"],"sourcesContent":["import { PureComponent, ReactNode } from 'react';\nimport {\n Unsubscribe,\n AbilityTuple,\n SubjectType,\n AnyAbility,\n Generics,\n Abilities,\n IfString,\n} from '@casl/ability';\n\nconst noop = () => {};\n\ntype AbilityCanProps<\n T extends Abilities,\n Else = IfString<T, { do: T } | { I: T }>\n> = T extends AbilityTuple\n ? { do: T[0], on: T[1], field?: string } |\n { I: T[0], a: Extract<T[1], SubjectType>, field?: string } |\n { I: T[0], an: Extract<T[1], SubjectType>, field?: string } |\n { I: T[0], this: Exclude<T[1], SubjectType>, field?: string }\n : Else;\n\ninterface ExtraProps {\n not?: boolean\n passThrough?: boolean\n}\n\ninterface CanExtraProps<T extends AnyAbility> extends ExtraProps {\n ability: T\n children: ReactNode | ((isAllowed: boolean, ability: T) => ReactNode)\n}\n\ninterface BoundCanExtraProps<T extends AnyAbility> extends ExtraProps {\n ability?: T\n children: ReactNode | ((isAllowed: boolean, ability: T) => ReactNode)\n}\n\nexport type CanProps<T extends AnyAbility> =\n AbilityCanProps<Generics<T>['abilities']> & CanExtraProps<T>;\nexport type BoundCanProps<T extends AnyAbility> =\n AbilityCanProps<Generics<T>['abilities']> & BoundCanExtraProps<T>;\n\nexport class Can<T extends AnyAbility> extends PureComponent<CanProps<T>, { t: boolean }> {\n private _isAllowed = false;\n private _ability: T | null = null;\n private _unsubscribeFromAbility: Unsubscribe = noop;\n state = { t: true }\n\n componentWillUnmount() {\n this._unsubscribeFromAbility();\n }\n\n private _connectToAbility(ability?: T) {\n if (ability === this._ability) {\n return;\n }\n\n this._unsubscribeFromAbility();\n this._ability = null;\n\n if (ability) {\n this._ability = ability;\n this._unsubscribeFromAbility = ability.on('updated', () => this.setState({ t: !this.state.t }));\n }\n }\n\n get allowed() {\n return this._isAllowed;\n }\n\n private _canRender(): boolean {\n const props: any = this.props;\n const subject = props.of || props.a || props.an || props.this || props.on;\n const can = props.not ? 'cannot' : 'can';\n\n return props.ability[can](props.I || props.do, subject, props.field);\n }\n\n render() {\n this._connectToAbility(this.props.ability);\n this._isAllowed = this._canRender();\n return this.props.passThrough || this._isAllowed ? this._renderChildren() : null;\n }\n\n private _renderChildren() {\n const { children, ability } = this.props;\n const elements = typeof children === 'function'\n ? children(this._isAllowed, ability as any)\n : children;\n\n return elements as ReactNode;\n }\n}\n","import { AnyAbility } from '@casl/ability';\nimport { Consumer, FunctionComponent, createElement } from 'react';\nimport { BoundCanProps, Can } from './Can';\n\nexport function createContextualCan<T extends AnyAbility>(\n Getter: Consumer<T>\n): FunctionComponent<BoundCanProps<T>> {\n return (props: BoundCanProps<T>) => createElement(Getter, {\n children: (ability: T) => \n createElement(Can, { ...props, ability: props.ability || ability } as any),\n });\n}\n","import React from 'react';\nimport { AnyAbility } from '@casl/ability';\n\nexport function useAbility<T extends AnyAbility>(context: React.Context<T>): T {\n const ability = React.useContext<T>(context);\n\n const subscribe = React.useCallback(\n (callback: () => void) => ability.on('updated', callback),\n [ability],\n );\n\n const getSnapshot = React.useCallback(() => ability.rules, [ability]);\n\n React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n return ability;\n}\n"],"names":["noop","Can","_PureComponent","_this","_len","arguments","length","args","Array","_key","call","apply","this","concat","_isAllowed","_ability","_unsubscribeFromAbility","state","t","_inheritsLoose","_proto","prototype","componentWillUnmount","_connectToAbility","ability","_this2","on","setState","_canRender","props","subject","of","a","an","can","not","I","do","field","render","passThrough","_renderChildren","_this$props","children","elements","_createClass","key","get","PureComponent","createContextualCan","Getter","createElement","_extends","useAbility","context","React","useContext","subscribe","useCallback","callback","getSnapshot","rules","useSyncExternalStore"],"mappings":"mxCAWA,IAAMA,EAAO,SAAPA,IAAc,EAgCpB,IAAaC,WAAGC,GAAA,SAAAD,IAAA,IAAAE,EAAA,IAAA,IAAAC,EAAAC,UAAAC,OAAAC,EAAA,IAAAC,MAAAJ,GAAAK,EAAA,EAAAA,EAAAL,EAAAK,IAAAF,EAAAE,GAAAJ,UAAAI,GAAAN,EAAAD,EAAAQ,KAAAC,MAAAT,EAAA,CAAAU,MAAAC,OAAAN,KAAAK,KAAAT,EACNW,EAAa,MAAKX,EAClBY,EAAqB,KAAIZ,EACzBa,EAAuChB,EAAIG,EACnDc,MAAQ,CAAEC,EAAG,MAAM,OAAAf,CAAA,CAAAgB,EAAAlB,EAAAC,GAAA,IAAAkB,EAAAnB,EAAAoB,UAAAD,EAEnBE,qBAAA,SAAAA,IACEV,KAAKI,GACP,EAACI,EAEOG,EAAR,SAAQA,kBAAkBC,GAAa,IAAAC,EAAAb,KACrC,GAAIY,IAAYZ,KAAKG,EACnB,OAGFH,KAAKI,IACLJ,KAAKG,EAAW,KAEhB,GAAIS,EAAS,CACXZ,KAAKG,EAAWS,EAChBZ,KAAKI,EAA0BQ,EAAQE,GAAG,UAAW,WAAA,OAAMD,EAAKE,SAAS,CAAET,GAAIO,EAAKR,MAAMC,GAAI,EAChG,CACF,EAACE,EAMOQ,EAAR,SAAQA,IACN,IAAMC,EAAajB,KAAKiB,MACxB,IAAMC,EAAUD,EAAME,IAAMF,EAAMG,GAAKH,EAAMI,IAAMJ,EAAMjB,MAAQiB,EAAMH,GACvE,IAAMQ,EAAML,EAAMM,IAAM,SAAW,MAEnC,OAAON,EAAML,QAAQU,GAAKL,EAAMO,GAAKP,EAAMQ,GAAIP,EAASD,EAAMS,MAChE,EAAClB,EAEDmB,OAAA,SAAAA,IACE3B,KAAKW,EAAkBX,KAAKiB,MAAML,SAClCZ,KAAKE,EAAaF,KAAKgB,IACvB,OAAOhB,KAAKiB,MAAMW,aAAe5B,KAAKE,EAAaF,KAAK6B,IAAoB,IAC9E,EAACrB,EAEOqB,EAAR,SAAQA,IACN,IAAAC,EAA8B9B,KAAKiB,MAA3Bc,EAAQD,EAARC,SAAUnB,EAAOkB,EAAPlB,QAClB,IAAMoB,SAAkBD,IAAa,WACjCA,EAAS/B,KAAKE,EAAYU,GAC1BmB,EAEJ,OAAOC,CACT,EAAC,OAAAC,EAAA5C,EAAA,CAAA,CAAA6C,IAAA,UAAAC,IAzBD,SAAAA,IACE,OAAOnC,KAAKE,CACd,IAAC,EA1B4CkC,EAAAA,eCvCxC,SAASC,EACdC,GAEA,OAAO,SAACrB,GAAuB,OAAKsB,EAAAA,cAAcD,EAAQ,CACxDP,SAAU,SAAVA,EAAWnB,GAAU,OACnB2B,EAAAA,cAAclD,EAAGmD,KAAOvB,EAAK,CAAEL,QAASK,EAAML,SAAWA,IAAiB,GAC5E,CACJ,CCRO,SAAS6B,WAAiCC,GAC/C,IAAM9B,EAAU+B,EAAMC,WAAcF,GAEpC,IAAMG,EAAYF,EAAMG,YACtB,SAACC,GAAoB,OAAKnC,EAAQE,GAAG,UAAWiC,EAAS,EACzD,CAACnC,IAGH,IAAMoC,EAAcL,EAAMG,YAAY,WAAA,OAAMlC,EAAQqC,KAAK,EAAE,CAACrC,IAE5D+B,EAAMO,qBAAqBL,EAAWG,EAAaA,GAEnD,OAAOpC,CACT"}
package/index.d.ts DELETED
@@ -1 +0,0 @@
1
- export * from './dist/types';