@bizapps/design-system-testing 1.0.11 → 1.7.1

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/dist/phone.cjs ADDED
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ var libphonenumberJs = require('libphonenumber-js');
4
+
5
+ // src/lib/phone.ts
6
+ function validatePhone(value) {
7
+ if (!value) {
8
+ return { isValid: false };
9
+ }
10
+ if (libphonenumberJs.isValidPhoneNumber(value)) {
11
+ return { isValid: true };
12
+ }
13
+ return { isValid: false, problem: libphonenumberJs.validatePhoneNumberLength(value) ?? "INVALID" };
14
+ }
15
+
16
+ exports.validatePhone = validatePhone;
17
+ //# sourceMappingURL=phone.cjs.map
18
+ //# sourceMappingURL=phone.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/phone.ts"],"names":["isValidPhoneNumber","validatePhoneNumberLength"],"mappings":";;;;;AAmCO,SAAS,cAAc,KAAA,EAAmC;AAC7D,EAAA,IAAI,CAAC,KAAA,EAAO;AACR,IAAA,OAAO,EAAE,SAAS,KAAA,EAAM;AAAA,EAC5B;AACA,EAAA,IAAIA,mCAAA,CAAmB,KAAK,CAAA,EAAG;AAC3B,IAAA,OAAO,EAAE,SAAS,IAAA,EAAK;AAAA,EAC3B;AACA,EAAA,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,SAASC,0CAAA,CAA0B,KAAK,KAAK,SAAA,EAAU;AACpF","file":"phone.cjs","sourcesContent":["import { isValidPhoneNumber, validatePhoneNumberLength } from 'libphonenumber-js';\n\n/**\n * Why a value is not a valid phone number. Mirrors `libphonenumber-js`'\n * `validatePhoneNumberLength` result, plus `'INVALID'` for a value whose length\n * IS possible for its calling code but which is not a real number (e.g. an\n * 8-digit Colombian value: 8 is a possible fixed-line length, yet `3008...` is\n * not an assignable fixed-line prefix).\n *\n * The design system emits this code, never a message: the copy belongs to the\n * consumer's i18n catalog.\n */\nexport type PhoneFieldProblem =\n | 'TOO_SHORT'\n | 'TOO_LONG'\n | 'INVALID_LENGTH'\n | 'INVALID_COUNTRY'\n | 'NOT_A_NUMBER'\n | 'INVALID';\n\nexport interface PhoneFieldValidity {\n /** True only when the value is a valid, assignable number. */\n isValid: boolean;\n /** Why it is invalid. Absent for a valid value AND for an empty one. */\n problem?: PhoneFieldProblem;\n}\n\n/**\n * Validates an E.164 phone value. Pure and stateless — safe to share between\n * the field, a Zod schema and a server route handler so the rule is written\n * once.\n *\n * An empty value reports `{ isValid: false }` with no `problem`: emptiness is\n * the consumer's `required` rule, not a phone problem.\n */\nexport function validatePhone(value: string): PhoneFieldValidity {\n if (!value) {\n return { isValid: false };\n }\n if (isValidPhoneNumber(value)) {\n return { isValid: true };\n }\n return { isValid: false, problem: validatePhoneNumberLength(value) ?? 'INVALID' };\n}\n"]}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Why a value is not a valid phone number. Mirrors `libphonenumber-js`'
3
+ * `validatePhoneNumberLength` result, plus `'INVALID'` for a value whose length
4
+ * IS possible for its calling code but which is not a real number (e.g. an
5
+ * 8-digit Colombian value: 8 is a possible fixed-line length, yet `3008...` is
6
+ * not an assignable fixed-line prefix).
7
+ *
8
+ * The design system emits this code, never a message: the copy belongs to the
9
+ * consumer's i18n catalog.
10
+ */
11
+ type PhoneFieldProblem = 'TOO_SHORT' | 'TOO_LONG' | 'INVALID_LENGTH' | 'INVALID_COUNTRY' | 'NOT_A_NUMBER' | 'INVALID';
12
+ interface PhoneFieldValidity {
13
+ /** True only when the value is a valid, assignable number. */
14
+ isValid: boolean;
15
+ /** Why it is invalid. Absent for a valid value AND for an empty one. */
16
+ problem?: PhoneFieldProblem;
17
+ }
18
+ /**
19
+ * Validates an E.164 phone value. Pure and stateless — safe to share between
20
+ * the field, a Zod schema and a server route handler so the rule is written
21
+ * once.
22
+ *
23
+ * An empty value reports `{ isValid: false }` with no `problem`: emptiness is
24
+ * the consumer's `required` rule, not a phone problem.
25
+ */
26
+ declare function validatePhone(value: string): PhoneFieldValidity;
27
+
28
+ export { type PhoneFieldProblem, type PhoneFieldValidity, validatePhone };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Why a value is not a valid phone number. Mirrors `libphonenumber-js`'
3
+ * `validatePhoneNumberLength` result, plus `'INVALID'` for a value whose length
4
+ * IS possible for its calling code but which is not a real number (e.g. an
5
+ * 8-digit Colombian value: 8 is a possible fixed-line length, yet `3008...` is
6
+ * not an assignable fixed-line prefix).
7
+ *
8
+ * The design system emits this code, never a message: the copy belongs to the
9
+ * consumer's i18n catalog.
10
+ */
11
+ type PhoneFieldProblem = 'TOO_SHORT' | 'TOO_LONG' | 'INVALID_LENGTH' | 'INVALID_COUNTRY' | 'NOT_A_NUMBER' | 'INVALID';
12
+ interface PhoneFieldValidity {
13
+ /** True only when the value is a valid, assignable number. */
14
+ isValid: boolean;
15
+ /** Why it is invalid. Absent for a valid value AND for an empty one. */
16
+ problem?: PhoneFieldProblem;
17
+ }
18
+ /**
19
+ * Validates an E.164 phone value. Pure and stateless — safe to share between
20
+ * the field, a Zod schema and a server route handler so the rule is written
21
+ * once.
22
+ *
23
+ * An empty value reports `{ isValid: false }` with no `problem`: emptiness is
24
+ * the consumer's `required` rule, not a phone problem.
25
+ */
26
+ declare function validatePhone(value: string): PhoneFieldValidity;
27
+
28
+ export { type PhoneFieldProblem, type PhoneFieldValidity, validatePhone };
package/dist/phone.js ADDED
@@ -0,0 +1,16 @@
1
+ import { isValidPhoneNumber, validatePhoneNumberLength } from 'libphonenumber-js';
2
+
3
+ // src/lib/phone.ts
4
+ function validatePhone(value) {
5
+ if (!value) {
6
+ return { isValid: false };
7
+ }
8
+ if (isValidPhoneNumber(value)) {
9
+ return { isValid: true };
10
+ }
11
+ return { isValid: false, problem: validatePhoneNumberLength(value) ?? "INVALID" };
12
+ }
13
+
14
+ export { validatePhone };
15
+ //# sourceMappingURL=phone.js.map
16
+ //# sourceMappingURL=phone.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/phone.ts"],"names":[],"mappings":";;;AAmCO,SAAS,cAAc,KAAA,EAAmC;AAC7D,EAAA,IAAI,CAAC,KAAA,EAAO;AACR,IAAA,OAAO,EAAE,SAAS,KAAA,EAAM;AAAA,EAC5B;AACA,EAAA,IAAI,kBAAA,CAAmB,KAAK,CAAA,EAAG;AAC3B,IAAA,OAAO,EAAE,SAAS,IAAA,EAAK;AAAA,EAC3B;AACA,EAAA,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,SAAS,yBAAA,CAA0B,KAAK,KAAK,SAAA,EAAU;AACpF","file":"phone.js","sourcesContent":["import { isValidPhoneNumber, validatePhoneNumberLength } from 'libphonenumber-js';\n\n/**\n * Why a value is not a valid phone number. Mirrors `libphonenumber-js`'\n * `validatePhoneNumberLength` result, plus `'INVALID'` for a value whose length\n * IS possible for its calling code but which is not a real number (e.g. an\n * 8-digit Colombian value: 8 is a possible fixed-line length, yet `3008...` is\n * not an assignable fixed-line prefix).\n *\n * The design system emits this code, never a message: the copy belongs to the\n * consumer's i18n catalog.\n */\nexport type PhoneFieldProblem =\n | 'TOO_SHORT'\n | 'TOO_LONG'\n | 'INVALID_LENGTH'\n | 'INVALID_COUNTRY'\n | 'NOT_A_NUMBER'\n | 'INVALID';\n\nexport interface PhoneFieldValidity {\n /** True only when the value is a valid, assignable number. */\n isValid: boolean;\n /** Why it is invalid. Absent for a valid value AND for an empty one. */\n problem?: PhoneFieldProblem;\n}\n\n/**\n * Validates an E.164 phone value. Pure and stateless — safe to share between\n * the field, a Zod schema and a server route handler so the rule is written\n * once.\n *\n * An empty value reports `{ isValid: false }` with no `problem`: emptiness is\n * the consumer's `required` rule, not a phone problem.\n */\nexport function validatePhone(value: string): PhoneFieldValidity {\n if (!value) {\n return { isValid: false };\n }\n if (isValidPhoneNumber(value)) {\n return { isValid: true };\n }\n return { isValid: false, problem: validatePhoneNumberLength(value) ?? 'INVALID' };\n}\n"]}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * AUTO-GENERATED — do not edit by hand.
3
+ *
4
+ * Regenerate with `pnpm generate-permissions`, which reads the catalog from the
5
+ * Nexus backend. The list is committed so the library build never depends on a
6
+ * live backend call.
7
+ *
8
+ * While this list is empty, `Permission` degrades to plain `string` and the
9
+ * development-time unknown-permission warning is skipped entirely — an empty
10
+ * catalog must never produce false positives.
11
+ */
12
+ declare const KNOWN_PERMISSIONS: readonly [];
13
+ type KnownPermission = (typeof KNOWN_PERMISSIONS)[number];
14
+
15
+ /**
16
+ * A permission string in `resource.action` or `module.resource.action`
17
+ * dot-notation, lowercase, with `*` as a segment wildcard and a bare `*` as the
18
+ * super-grant.
19
+ *
20
+ * Known catalog entries are suggested by autocomplete; any other string is still
21
+ * accepted, so a permission the catalog has not caught up with never blocks a
22
+ * build. The `(string & {})` half is the TypeScript idiom that keeps the literal
23
+ * suggestions from collapsing into plain `string`.
24
+ */
25
+ type Permission = KnownPermission | (string & {});
26
+
27
+ /**
28
+ * The server-safe half of the authorization system.
29
+ *
30
+ * Everything in this module is a pure function over plain data: no React, no hooks, no
31
+ * context, no module-level mutable state. That is not incidental — it is the contract.
32
+ * `src/policy.ts` re-exports this file as the `@bizapps/ui/policy` entry point, which
33
+ * tsup deliberately does NOT stamp with `"use client"`, so a Next.js Server Component,
34
+ * Route Handler or Server Action can import it and evaluate permissions on the server.
35
+ *
36
+ * **Never import React (or anything that transitively does) into this file.** The build
37
+ * asserts it: `tsup.config.ts` fails if `dist/policy.js` contains a react import or a
38
+ * `"use client"` directive. Breaking that turns every server-side caller into a client
39
+ * reference, silently.
40
+ *
41
+ * The client half — `AbilityProvider`, `useAbility`, `useCan`, `Can` — consumes these
42
+ * same functions, so there is exactly one implementation of the matching rules. Two
43
+ * implementations of an authorization primitive drift, and drift on a security boundary
44
+ * is invisible until it matters.
45
+ */
46
+
47
+ /**
48
+ * Tests one entry of the user's grant set against one requested permission.
49
+ *
50
+ * @param pattern - an entry from the user's grant set; may contain `*` as a segment
51
+ * wildcard, or be a bare `*` meaning "everything"
52
+ * @param permission - the concrete permission being requested
53
+ *
54
+ * Matching is arity-strict: a grant of `users.*` covers `users.delete`, but a grant of
55
+ * `users` does NOT, and neither covers `users.delete.confirm`. That rules out the
56
+ * ambiguity of whether `users` names the resource or the namespace.
57
+ */
58
+ declare function matchPermission(pattern: string, permission: string): boolean;
59
+ /** Whether any entry in the grant set covers the requested permission. */
60
+ declare function matchesAny(permissionSet: Set<string>, permission: string): boolean;
61
+ /**
62
+ * Evaluates a permission requirement against a grant set.
63
+ *
64
+ * The order of the first two checks is load-bearing and must not be swapped: an empty
65
+ * required list denies **even for a super-grant holder**, because an empty list means
66
+ * "nothing satisfies this", not "anything does".
67
+ *
68
+ * @param permissionSet - the user's grants
69
+ * @param perms - the permission(s) required
70
+ * @param mode - with an array, `'any'` (default) passes when at least one matches,
71
+ * `'all'` requires every one
72
+ */
73
+ declare function can(permissionSet: Set<string>, perms: Permission | Permission[], mode?: 'any' | 'all'): boolean;
74
+
75
+ export { KNOWN_PERMISSIONS as K, type Permission as P, type KnownPermission as a, matchesAny as b, can as c, matchPermission as m };
@@ -0,0 +1,75 @@
1
+ /**
2
+ * AUTO-GENERATED — do not edit by hand.
3
+ *
4
+ * Regenerate with `pnpm generate-permissions`, which reads the catalog from the
5
+ * Nexus backend. The list is committed so the library build never depends on a
6
+ * live backend call.
7
+ *
8
+ * While this list is empty, `Permission` degrades to plain `string` and the
9
+ * development-time unknown-permission warning is skipped entirely — an empty
10
+ * catalog must never produce false positives.
11
+ */
12
+ declare const KNOWN_PERMISSIONS: readonly [];
13
+ type KnownPermission = (typeof KNOWN_PERMISSIONS)[number];
14
+
15
+ /**
16
+ * A permission string in `resource.action` or `module.resource.action`
17
+ * dot-notation, lowercase, with `*` as a segment wildcard and a bare `*` as the
18
+ * super-grant.
19
+ *
20
+ * Known catalog entries are suggested by autocomplete; any other string is still
21
+ * accepted, so a permission the catalog has not caught up with never blocks a
22
+ * build. The `(string & {})` half is the TypeScript idiom that keeps the literal
23
+ * suggestions from collapsing into plain `string`.
24
+ */
25
+ type Permission = KnownPermission | (string & {});
26
+
27
+ /**
28
+ * The server-safe half of the authorization system.
29
+ *
30
+ * Everything in this module is a pure function over plain data: no React, no hooks, no
31
+ * context, no module-level mutable state. That is not incidental — it is the contract.
32
+ * `src/policy.ts` re-exports this file as the `@bizapps/ui/policy` entry point, which
33
+ * tsup deliberately does NOT stamp with `"use client"`, so a Next.js Server Component,
34
+ * Route Handler or Server Action can import it and evaluate permissions on the server.
35
+ *
36
+ * **Never import React (or anything that transitively does) into this file.** The build
37
+ * asserts it: `tsup.config.ts` fails if `dist/policy.js` contains a react import or a
38
+ * `"use client"` directive. Breaking that turns every server-side caller into a client
39
+ * reference, silently.
40
+ *
41
+ * The client half — `AbilityProvider`, `useAbility`, `useCan`, `Can` — consumes these
42
+ * same functions, so there is exactly one implementation of the matching rules. Two
43
+ * implementations of an authorization primitive drift, and drift on a security boundary
44
+ * is invisible until it matters.
45
+ */
46
+
47
+ /**
48
+ * Tests one entry of the user's grant set against one requested permission.
49
+ *
50
+ * @param pattern - an entry from the user's grant set; may contain `*` as a segment
51
+ * wildcard, or be a bare `*` meaning "everything"
52
+ * @param permission - the concrete permission being requested
53
+ *
54
+ * Matching is arity-strict: a grant of `users.*` covers `users.delete`, but a grant of
55
+ * `users` does NOT, and neither covers `users.delete.confirm`. That rules out the
56
+ * ambiguity of whether `users` names the resource or the namespace.
57
+ */
58
+ declare function matchPermission(pattern: string, permission: string): boolean;
59
+ /** Whether any entry in the grant set covers the requested permission. */
60
+ declare function matchesAny(permissionSet: Set<string>, permission: string): boolean;
61
+ /**
62
+ * Evaluates a permission requirement against a grant set.
63
+ *
64
+ * The order of the first two checks is load-bearing and must not be swapped: an empty
65
+ * required list denies **even for a super-grant holder**, because an empty list means
66
+ * "nothing satisfies this", not "anything does".
67
+ *
68
+ * @param permissionSet - the user's grants
69
+ * @param perms - the permission(s) required
70
+ * @param mode - with an array, `'any'` (default) passes when at least one matches,
71
+ * `'all'` requires every one
72
+ */
73
+ declare function can(permissionSet: Set<string>, perms: Permission | Permission[], mode?: 'any' | 'all'): boolean;
74
+
75
+ export { KNOWN_PERMISSIONS as K, type Permission as P, type KnownPermission as a, matchesAny as b, can as c, matchPermission as m };
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ // src/components/authorization/policy.ts
4
+ function matchPermission(pattern, permission) {
5
+ if (pattern === "*") return true;
6
+ if (pattern === permission) return true;
7
+ const patternParts = pattern.split(".");
8
+ const permParts = permission.split(".");
9
+ if (patternParts.length !== permParts.length) return false;
10
+ return patternParts.every((part, i) => part === "*" || part === permParts[i]);
11
+ }
12
+ function matchesAny(permissionSet, permission) {
13
+ if (permissionSet.has(permission)) return true;
14
+ for (const pattern of permissionSet) {
15
+ if (matchPermission(pattern, permission)) return true;
16
+ }
17
+ return false;
18
+ }
19
+ function can(permissionSet, perms, mode = "any") {
20
+ const list = Array.isArray(perms) ? perms : [perms];
21
+ if (list.length === 0) return false;
22
+ if (permissionSet.has("*")) return true;
23
+ if (mode === "all") {
24
+ return list.every((p) => matchesAny(permissionSet, p));
25
+ }
26
+ return list.some((p) => matchesAny(permissionSet, p));
27
+ }
28
+
29
+ exports.can = can;
30
+ exports.matchPermission = matchPermission;
31
+ exports.matchesAny = matchesAny;
32
+ //# sourceMappingURL=policy.cjs.map
33
+ //# sourceMappingURL=policy.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/authorization/policy.ts"],"names":[],"mappings":";;;AAgCA,SAAS,eAAA,CAAgB,SAAiB,UAAA,EAA6B;AACnE,EAAA,IAAI,OAAA,KAAY,KAAK,OAAO,IAAA;AAC5B,EAAA,IAAI,OAAA,KAAY,YAAY,OAAO,IAAA;AACnC,EAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,KAAA,CAAM,GAAG,CAAA;AACtC,EAAA,MAAM,SAAA,GAAY,UAAA,CAAW,KAAA,CAAM,GAAG,CAAA;AACtC,EAAA,IAAI,YAAA,CAAa,MAAA,KAAW,SAAA,CAAU,MAAA,EAAQ,OAAO,KAAA;AACrD,EAAA,OAAO,YAAA,CAAa,KAAA,CAAM,CAAC,IAAA,EAAM,CAAA,KAAM,SAAS,GAAA,IAAO,IAAA,KAAS,SAAA,CAAU,CAAC,CAAC,CAAA;AAChF;AAGA,SAAS,UAAA,CAAW,eAA4B,UAAA,EAA6B;AACzE,EAAA,IAAI,aAAA,CAAc,GAAA,CAAI,UAAU,CAAA,EAAG,OAAO,IAAA;AAC1C,EAAA,KAAA,MAAW,WAAW,aAAA,EAAe;AACjC,IAAA,IAAI,eAAA,CAAgB,OAAA,EAAS,UAAU,CAAA,EAAG,OAAO,IAAA;AAAA,EACrD;AACA,EAAA,OAAO,KAAA;AACX;AAcA,SAAS,GAAA,CACL,aAAA,EACA,KAAA,EACA,IAAA,GAAsB,KAAA,EACf;AACP,EAAA,MAAM,OAAO,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,GAAI,KAAA,GAAQ,CAAC,KAAK,CAAA;AAClD,EAAA,IAAI,IAAA,CAAK,MAAA,KAAW,CAAA,EAAG,OAAO,KAAA;AAC9B,EAAA,IAAI,aAAA,CAAc,GAAA,CAAI,GAAG,CAAA,EAAG,OAAO,IAAA;AACnC,EAAA,IAAI,SAAS,KAAA,EAAO;AAChB,IAAA,OAAO,KAAK,KAAA,CAAM,CAAC,MAAM,UAAA,CAAW,aAAA,EAAe,CAAC,CAAC,CAAA;AAAA,EACzD;AACA,EAAA,OAAO,KAAK,IAAA,CAAK,CAAC,MAAM,UAAA,CAAW,aAAA,EAAe,CAAC,CAAC,CAAA;AACxD","file":"policy.cjs","sourcesContent":["/**\n * The server-safe half of the authorization system.\n *\n * Everything in this module is a pure function over plain data: no React, no hooks, no\n * context, no module-level mutable state. That is not incidental — it is the contract.\n * `src/policy.ts` re-exports this file as the `@bizapps/ui/policy` entry point, which\n * tsup deliberately does NOT stamp with `\"use client\"`, so a Next.js Server Component,\n * Route Handler or Server Action can import it and evaluate permissions on the server.\n *\n * **Never import React (or anything that transitively does) into this file.** The build\n * asserts it: `tsup.config.ts` fails if `dist/policy.js` contains a react import or a\n * `\"use client\"` directive. Breaking that turns every server-side caller into a client\n * reference, silently.\n *\n * The client half — `AbilityProvider`, `useAbility`, `useCan`, `Can` — consumes these\n * same functions, so there is exactly one implementation of the matching rules. Two\n * implementations of an authorization primitive drift, and drift on a security boundary\n * is invisible until it matters.\n */\nimport type { Permission } from './permission-catalog';\n\n/**\n * Tests one entry of the user's grant set against one requested permission.\n *\n * @param pattern - an entry from the user's grant set; may contain `*` as a segment\n * wildcard, or be a bare `*` meaning \"everything\"\n * @param permission - the concrete permission being requested\n *\n * Matching is arity-strict: a grant of `users.*` covers `users.delete`, but a grant of\n * `users` does NOT, and neither covers `users.delete.confirm`. That rules out the\n * ambiguity of whether `users` names the resource or the namespace.\n */\nfunction matchPermission(pattern: string, permission: string): boolean {\n if (pattern === '*') return true;\n if (pattern === permission) return true;\n const patternParts = pattern.split('.');\n const permParts = permission.split('.');\n if (patternParts.length !== permParts.length) return false;\n return patternParts.every((part, i) => part === '*' || part === permParts[i]);\n}\n\n/** Whether any entry in the grant set covers the requested permission. */\nfunction matchesAny(permissionSet: Set<string>, permission: string): boolean {\n if (permissionSet.has(permission)) return true;\n for (const pattern of permissionSet) {\n if (matchPermission(pattern, permission)) return true;\n }\n return false;\n}\n\n/**\n * Evaluates a permission requirement against a grant set.\n *\n * The order of the first two checks is load-bearing and must not be swapped: an empty\n * required list denies **even for a super-grant holder**, because an empty list means\n * \"nothing satisfies this\", not \"anything does\".\n *\n * @param permissionSet - the user's grants\n * @param perms - the permission(s) required\n * @param mode - with an array, `'any'` (default) passes when at least one matches,\n * `'all'` requires every one\n */\nfunction can(\n permissionSet: Set<string>,\n perms: Permission | Permission[],\n mode: 'any' | 'all' = 'any',\n): boolean {\n const list = Array.isArray(perms) ? perms : [perms];\n if (list.length === 0) return false;\n if (permissionSet.has('*')) return true;\n if (mode === 'all') {\n return list.every((p) => matchesAny(permissionSet, p));\n }\n return list.some((p) => matchesAny(permissionSet, p));\n}\n\nexport { can, matchPermission, matchesAny };\n"]}
@@ -0,0 +1 @@
1
+ export { P as Permission, c as can, m as matchPermission, b as matchesAny } from './policy-DPpMNLiO.cjs';
@@ -0,0 +1 @@
1
+ export { P as Permission, c as can, m as matchPermission, b as matchesAny } from './policy-DPpMNLiO.js';
package/dist/policy.js ADDED
@@ -0,0 +1,29 @@
1
+ // src/components/authorization/policy.ts
2
+ function matchPermission(pattern, permission) {
3
+ if (pattern === "*") return true;
4
+ if (pattern === permission) return true;
5
+ const patternParts = pattern.split(".");
6
+ const permParts = permission.split(".");
7
+ if (patternParts.length !== permParts.length) return false;
8
+ return patternParts.every((part, i) => part === "*" || part === permParts[i]);
9
+ }
10
+ function matchesAny(permissionSet, permission) {
11
+ if (permissionSet.has(permission)) return true;
12
+ for (const pattern of permissionSet) {
13
+ if (matchPermission(pattern, permission)) return true;
14
+ }
15
+ return false;
16
+ }
17
+ function can(permissionSet, perms, mode = "any") {
18
+ const list = Array.isArray(perms) ? perms : [perms];
19
+ if (list.length === 0) return false;
20
+ if (permissionSet.has("*")) return true;
21
+ if (mode === "all") {
22
+ return list.every((p) => matchesAny(permissionSet, p));
23
+ }
24
+ return list.some((p) => matchesAny(permissionSet, p));
25
+ }
26
+
27
+ export { can, matchPermission, matchesAny };
28
+ //# sourceMappingURL=policy.js.map
29
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/authorization/policy.ts"],"names":[],"mappings":";AAgCA,SAAS,eAAA,CAAgB,SAAiB,UAAA,EAA6B;AACnE,EAAA,IAAI,OAAA,KAAY,KAAK,OAAO,IAAA;AAC5B,EAAA,IAAI,OAAA,KAAY,YAAY,OAAO,IAAA;AACnC,EAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,KAAA,CAAM,GAAG,CAAA;AACtC,EAAA,MAAM,SAAA,GAAY,UAAA,CAAW,KAAA,CAAM,GAAG,CAAA;AACtC,EAAA,IAAI,YAAA,CAAa,MAAA,KAAW,SAAA,CAAU,MAAA,EAAQ,OAAO,KAAA;AACrD,EAAA,OAAO,YAAA,CAAa,KAAA,CAAM,CAAC,IAAA,EAAM,CAAA,KAAM,SAAS,GAAA,IAAO,IAAA,KAAS,SAAA,CAAU,CAAC,CAAC,CAAA;AAChF;AAGA,SAAS,UAAA,CAAW,eAA4B,UAAA,EAA6B;AACzE,EAAA,IAAI,aAAA,CAAc,GAAA,CAAI,UAAU,CAAA,EAAG,OAAO,IAAA;AAC1C,EAAA,KAAA,MAAW,WAAW,aAAA,EAAe;AACjC,IAAA,IAAI,eAAA,CAAgB,OAAA,EAAS,UAAU,CAAA,EAAG,OAAO,IAAA;AAAA,EACrD;AACA,EAAA,OAAO,KAAA;AACX;AAcA,SAAS,GAAA,CACL,aAAA,EACA,KAAA,EACA,IAAA,GAAsB,KAAA,EACf;AACP,EAAA,MAAM,OAAO,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,GAAI,KAAA,GAAQ,CAAC,KAAK,CAAA;AAClD,EAAA,IAAI,IAAA,CAAK,MAAA,KAAW,CAAA,EAAG,OAAO,KAAA;AAC9B,EAAA,IAAI,aAAA,CAAc,GAAA,CAAI,GAAG,CAAA,EAAG,OAAO,IAAA;AACnC,EAAA,IAAI,SAAS,KAAA,EAAO;AAChB,IAAA,OAAO,KAAK,KAAA,CAAM,CAAC,MAAM,UAAA,CAAW,aAAA,EAAe,CAAC,CAAC,CAAA;AAAA,EACzD;AACA,EAAA,OAAO,KAAK,IAAA,CAAK,CAAC,MAAM,UAAA,CAAW,aAAA,EAAe,CAAC,CAAC,CAAA;AACxD","file":"policy.js","sourcesContent":["/**\n * The server-safe half of the authorization system.\n *\n * Everything in this module is a pure function over plain data: no React, no hooks, no\n * context, no module-level mutable state. That is not incidental — it is the contract.\n * `src/policy.ts` re-exports this file as the `@bizapps/ui/policy` entry point, which\n * tsup deliberately does NOT stamp with `\"use client\"`, so a Next.js Server Component,\n * Route Handler or Server Action can import it and evaluate permissions on the server.\n *\n * **Never import React (or anything that transitively does) into this file.** The build\n * asserts it: `tsup.config.ts` fails if `dist/policy.js` contains a react import or a\n * `\"use client\"` directive. Breaking that turns every server-side caller into a client\n * reference, silently.\n *\n * The client half — `AbilityProvider`, `useAbility`, `useCan`, `Can` — consumes these\n * same functions, so there is exactly one implementation of the matching rules. Two\n * implementations of an authorization primitive drift, and drift on a security boundary\n * is invisible until it matters.\n */\nimport type { Permission } from './permission-catalog';\n\n/**\n * Tests one entry of the user's grant set against one requested permission.\n *\n * @param pattern - an entry from the user's grant set; may contain `*` as a segment\n * wildcard, or be a bare `*` meaning \"everything\"\n * @param permission - the concrete permission being requested\n *\n * Matching is arity-strict: a grant of `users.*` covers `users.delete`, but a grant of\n * `users` does NOT, and neither covers `users.delete.confirm`. That rules out the\n * ambiguity of whether `users` names the resource or the namespace.\n */\nfunction matchPermission(pattern: string, permission: string): boolean {\n if (pattern === '*') return true;\n if (pattern === permission) return true;\n const patternParts = pattern.split('.');\n const permParts = permission.split('.');\n if (patternParts.length !== permParts.length) return false;\n return patternParts.every((part, i) => part === '*' || part === permParts[i]);\n}\n\n/** Whether any entry in the grant set covers the requested permission. */\nfunction matchesAny(permissionSet: Set<string>, permission: string): boolean {\n if (permissionSet.has(permission)) return true;\n for (const pattern of permissionSet) {\n if (matchPermission(pattern, permission)) return true;\n }\n return false;\n}\n\n/**\n * Evaluates a permission requirement against a grant set.\n *\n * The order of the first two checks is load-bearing and must not be swapped: an empty\n * required list denies **even for a super-grant holder**, because an empty list means\n * \"nothing satisfies this\", not \"anything does\".\n *\n * @param permissionSet - the user's grants\n * @param perms - the permission(s) required\n * @param mode - with an array, `'any'` (default) passes when at least one matches,\n * `'all'` requires every one\n */\nfunction can(\n permissionSet: Set<string>,\n perms: Permission | Permission[],\n mode: 'any' | 'all' = 'any',\n): boolean {\n const list = Array.isArray(perms) ? perms : [perms];\n if (list.length === 0) return false;\n if (permissionSet.has('*')) return true;\n if (mode === 'all') {\n return list.every((p) => matchesAny(permissionSet, p));\n }\n return list.some((p) => matchesAny(permissionSet, p));\n}\n\nexport { can, matchPermission, matchesAny };\n"]}
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.0.11",
6
+ "version": "1.7.1",
7
7
  "description": "This package provides reusable UI components to ensure consistency across all CRM frontend applications",
8
8
  "author": "BizAppsTotal <bizapps@tcce.biz>",
9
9
  "type": "module",
@@ -19,17 +19,24 @@
19
19
  "import": "./dist/main.js",
20
20
  "require": "./dist/main.cjs"
21
21
  },
22
+ "./policy": {
23
+ "types": "./dist/policy.d.ts",
24
+ "import": "./dist/policy.js",
25
+ "require": "./dist/policy.cjs"
26
+ },
27
+ "./phone": {
28
+ "types": "./dist/phone.d.ts",
29
+ "import": "./dist/phone.js",
30
+ "require": "./dist/phone.cjs"
31
+ },
32
+ "./editor": {
33
+ "types": "./dist/editor.d.ts",
34
+ "import": "./dist/editor.js",
35
+ "require": "./dist/editor.cjs"
36
+ },
22
37
  "./styles": "./dist/main.css"
23
38
  },
24
39
  "peerDependencies": {
25
- "@tanstack/react-table": "^8.21.3",
26
- "motion": "^12.23.13",
27
- "react": "^19.1.1",
28
- "react-dom": "^19.1.1",
29
- "react-hook-form": "^7.77.0",
30
- "radix-ui": "^1.4.3",
31
- "lucide-react": "^1.16.0",
32
- "next-themes": "^0.4.6",
33
40
  "@hookform/resolvers": "^5.4.0",
34
41
  "@schedule-x/calendar": "4.6.0",
35
42
  "@schedule-x/calendar-controls": "4.6.0",
@@ -37,10 +44,26 @@
37
44
  "@schedule-x/events-service": "4.6.0",
38
45
  "@schedule-x/react": "4.1.0",
39
46
  "@schedule-x/theme-default": "4.6.0",
40
- "react-day-picker": "^10.0.1",
47
+ "@tanstack/react-table": "^8.21.3",
48
+ "@tiptap/core": "^3.30.5",
49
+ "@tiptap/extension-highlight": "^3.30.5",
50
+ "@tiptap/extension-image": "^3.30.5",
51
+ "@tiptap/extension-link": "^3.30.5",
52
+ "@tiptap/extension-table": "^3.30.5",
53
+ "@tiptap/pm": "^3.30.5",
54
+ "@tiptap/react": "^3.30.5",
55
+ "@tiptap/starter-kit": "^3.30.5",
41
56
  "embla-carousel-react": "^8.6.0",
42
- "zod": "^4.4.3",
43
- "sonner": "^2.0.7"
57
+ "lucide-react": "^1.16.0",
58
+ "motion": "^12.23.13",
59
+ "next-themes": "^0.4.6",
60
+ "radix-ui": "^1.4.3",
61
+ "react": "^19.1.1",
62
+ "react-day-picker": "^10.0.1",
63
+ "react-dom": "^19.1.1",
64
+ "react-hook-form": "^7.77.0",
65
+ "sonner": "^2.0.7",
66
+ "zod": "^4.4.3"
44
67
  },
45
68
  "peerDependenciesMeta": {
46
69
  "@hookform/resolvers": {
@@ -67,6 +90,30 @@
67
90
  "@tanstack/react-table": {
68
91
  "optional": true
69
92
  },
93
+ "@tiptap/core": {
94
+ "optional": true
95
+ },
96
+ "@tiptap/extension-highlight": {
97
+ "optional": true
98
+ },
99
+ "@tiptap/extension-image": {
100
+ "optional": true
101
+ },
102
+ "@tiptap/extension-link": {
103
+ "optional": true
104
+ },
105
+ "@tiptap/extension-table": {
106
+ "optional": true
107
+ },
108
+ "@tiptap/pm": {
109
+ "optional": true
110
+ },
111
+ "@tiptap/react": {
112
+ "optional": true
113
+ },
114
+ "@tiptap/starter-kit": {
115
+ "optional": true
116
+ },
70
117
  "embla-carousel-react": {
71
118
  "optional": true
72
119
  },
@@ -102,10 +149,23 @@
102
149
  "@storybook/react-vite": "^10.2.17",
103
150
  "@tailwindcss/vite": "^4.2.1",
104
151
  "@tanstack/react-table": "^8.21.3",
152
+ "@testing-library/jest-dom": "^7.0.0",
153
+ "@testing-library/react": "^16.3.2",
154
+ "@testing-library/user-event": "^14.6.1",
155
+ "@tiptap/core": "3.30.5",
156
+ "@tiptap/extension-highlight": "3.30.5",
157
+ "@tiptap/extension-image": "3.30.5",
158
+ "@tiptap/extension-link": "3.30.5",
159
+ "@tiptap/extension-table": "3.30.5",
160
+ "@tiptap/pm": "3.30.5",
161
+ "@tiptap/react": "3.30.5",
162
+ "@tiptap/starter-kit": "3.30.5",
105
163
  "@types/node": "^24.12.0",
106
164
  "@types/react": "^19.2.14",
107
165
  "@types/react-dom": "^19.2.3",
108
166
  "@vitejs/plugin-react": "^5.1.4",
167
+ "@vitest/coverage-v8": "^4.1.10",
168
+ "axe-core": "^4.12.1",
109
169
  "eslint": "^9.39.4",
110
170
  "eslint-config-prettier": "^10.1.8",
111
171
  "eslint-plugin-react": "^7.37.5",
@@ -113,6 +173,7 @@
113
173
  "eslint-plugin-react-refresh": "^0.4.26",
114
174
  "eslint-plugin-storybook": "^10.2.17",
115
175
  "globals": "^16.5.0",
176
+ "jsdom": "^30.0.1",
116
177
  "motion": "^12.40.0",
117
178
  "prettier": "^3.8.1",
118
179
  "prettier-plugin-tailwindcss": "^0.6.14",
@@ -123,19 +184,24 @@
123
184
  "storybook": "^10.2.17",
124
185
  "tailwindcss": "^4.2.1",
125
186
  "ts-morph": "^24.0.0",
126
- "tsx": "^4.19.4",
127
187
  "tsup": "^8.5.1",
188
+ "tsx": "^4.19.4",
128
189
  "typescript": "~5.8.3",
129
190
  "typescript-eslint": "8.53.0",
130
191
  "vite": "^7.3.1",
131
- "vite-plugin-dts": "^4.5.4"
192
+ "vite-plugin-dts": "^4.5.4",
193
+ "vitest": "^4.1.10",
194
+ "vitest-axe": "^0.1.0",
195
+ "zod": "^4.4.3"
132
196
  },
133
197
  "dependencies": {
134
198
  "class-variance-authority": "^0.7.1",
135
199
  "clsx": "^2.1.1",
136
200
  "cmdk": "^1.1.1",
137
201
  "color": "^5.0.3",
202
+ "country-flag-icons": "^1.6.20",
138
203
  "date-fns": "^4.4.0",
204
+ "libphonenumber-js": "^1.13.10",
139
205
  "react-dropzone": "^15.0.0",
140
206
  "tailwind-merge": "^3.6.0",
141
207
  "temporal-polyfill": "0.3.2",
@@ -146,12 +212,16 @@
146
212
  "build": "tsup && vite build --config vite.lib.config.ts",
147
213
  "build:storybook": "storybook build",
148
214
  "generate-catalog": "tsx scripts/generate-catalog.ts",
215
+ "generate-permissions": "tsx scripts/generate-permissions.ts",
149
216
  "type-check": "tsc -p tsconfig.build.json --noEmit",
150
217
  "lint": "eslint .",
151
218
  "lint:fix": "eslint . --fix",
152
219
  "format": "prettier --write .",
153
220
  "preview": "vite preview",
154
221
  "storybook": "storybook dev -p 6006 --host 0.0.0.0 --no-open",
155
- "build-storybook": "storybook build"
222
+ "build-storybook": "storybook build",
223
+ "test": "vitest run",
224
+ "test:watch": "vitest",
225
+ "test:coverage": "vitest run --coverage"
156
226
  }
157
227
  }