@consenti/ui 0.1.0 → 0.2.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/dist/react.d.ts CHANGED
@@ -1,13 +1,54 @@
1
+ type NonEmptyArray<T> = [T, ...T[]];
2
+ type DeepPartial<T> = T extends Function ? T : T extends Array<infer InferredArrayType> ? Array<DeepPartial<InferredArrayType>> : T extends ReadonlyArray<infer InferredReadonlyArrayType> ? ReadonlyArray<DeepPartial<InferredReadonlyArrayType>> : T extends object ? {
3
+ [Key in keyof T]?: DeepPartial<T[Key]>;
4
+ } : T;
5
+
6
+ declare const COMPLIANCE_GROUP_IDS: readonly ["opt-in", "opt-out", "opt-out-strict", "opt-in-dpdpa", "opt-in-china", "opt-in-brazil", "general-privacy-consent", "notice-only"];
7
+
8
+ /**
9
+ * Compliance-related literal types, derived from the regulation/group ID data
10
+ * that lives in @consenti/utils (packages/utils/src/compliance.ts).
11
+ *
12
+ * This import is type-only and fully erased at compile time — @consenti/types
13
+ * has no runtime dependency on @consenti/utils. The actual data (COMPLIANCE_GROUPS,
14
+ * EMBEDDED_COMPLIANCE_MAP, COMPLIANCE_VALIDATION_RULES, GPC_OPTIONS, etc.) lives in
15
+ * @consenti/utils; import it from there.
16
+ *
17
+ * HOW TO ADD A NEW REGULATION OR COMPLIANCE GROUP: see the header comment in
18
+ * packages/utils/src/compliance.ts — these types auto-update from that data.
19
+ */
20
+
21
+ type ComplianceGroupId = typeof COMPLIANCE_GROUP_IDS[number];
22
+
23
+ interface ComplianceMapData {
24
+ version: string;
25
+ countries: Record<string, CountryComplianceEntry>;
26
+ }
27
+ interface CountryComplianceEntry {
28
+ complianceGroup: ComplianceGroupId;
29
+ default: ComplianceGroupId;
30
+ description: string;
31
+ overriddenRegions?: Record<string, {
32
+ complianceGroup: ComplianceGroupId;
33
+ description: string;
34
+ }>;
35
+ }
36
+
1
37
  type ConsentStatus = 'granted' | 'denied' | 'objected';
38
+ type ConsentAction = 'accept_all' | 'reject_all' | 'custom' | 'update';
2
39
  type ConsentValue = Record<string, ConsentStatus>;
3
40
  interface Cookie {
4
41
  id: string;
5
- type?: 'consent' | 'legitimate_interest';
42
+ legalBasis?: 'mandatory' | 'consent' | 'legitimate_interest';
43
+ /** @deprecated use legalBasis: 'mandatory' */
6
44
  mandatory?: boolean;
45
+ /** @deprecated use legalBasis */
46
+ type?: 'consent' | 'legitimate_interest';
7
47
  listenGpc?: boolean;
8
48
  expiry?: number;
9
49
  tcfVendorId?: number;
10
50
  tcfPurposes?: number[];
51
+ tcfSpecialFeatures?: number[];
11
52
  cpraCategory?: 'sale' | 'sharing' | 'sensitive';
12
53
  }
13
54
  type ButtonStyle = 'primary' | 'secondary' | 'text' | 'accent';
@@ -18,6 +59,7 @@ interface Button {
18
59
  action: ButtonAction;
19
60
  cookies?: string[] | '*' | '!';
20
61
  url?: string;
62
+ type?: string;
21
63
  }
22
64
  interface MainBanner {
23
65
  position: 'top' | 'bottom' | 'middle' | 'left-bottom' | 'right-bottom';
@@ -43,18 +85,13 @@ interface Category {
43
85
  };
44
86
  cookies: string[];
45
87
  }
46
- interface PreferenceModal {
88
+ interface PreferenceModal extends Omit<MainBanner, "position"> {
47
89
  position?: 'left' | 'right' | 'center';
48
- overlayOpacity?: number;
49
- showClose?: boolean;
50
- showLocaleSwitcher?: boolean;
51
- heading?: string;
52
- headingTag?: string;
53
90
  subheading?: string;
54
- htmlText?: string;
55
- buttons: Button[];
56
91
  categories: Category[];
57
92
  persistent?: boolean;
93
+ /** Screen width in px below which the modal expands to full screen. Default: 576. Set to 0 to disable. */
94
+ mobileFullScreenBreakpoint?: number;
58
95
  }
59
96
  interface DpdpaConfig {
60
97
  dataFiduciary: string;
@@ -63,6 +100,7 @@ interface DpdpaConfig {
63
100
  }
64
101
  interface ResolvedProfile {
65
102
  id: number;
103
+ profileUuid?: string;
66
104
  version: number;
67
105
  defaultLocale: string;
68
106
  locales?: string[];
@@ -73,6 +111,26 @@ interface ResolvedProfile {
73
111
  cookieSigningKey?: string;
74
112
  dpdpa?: DpdpaConfig;
75
113
  darkMode?: boolean;
114
+ gpcMode?: 'ignore' | 'honor' | 'strict';
115
+ complianceGroup?: string;
116
+ complianceConfig?: Record<string, string>;
117
+ }
118
+ type WidgetCountryResolverFn = () => Promise<{
119
+ country: string | null;
120
+ region: string | null;
121
+ confidence: number;
122
+ }>;
123
+ interface AgeGateWidgetConfig {
124
+ enabled: boolean;
125
+ minimumAge: number;
126
+ requireParentalConsent?: boolean;
127
+ }
128
+ interface ComplianceWidgetConfig {
129
+ type?: 'auto' | ComplianceGroupId | string;
130
+ geoDataProvider?: 'default' | WidgetCountryResolverFn;
131
+ autoComplianceMap?: 'default' | ComplianceMapData | 'auto';
132
+ complianceMapUrl?: string;
133
+ ageGate?: AgeGateWidgetConfig;
76
134
  }
77
135
  interface GtmConfig {
78
136
  containerId?: string;
@@ -88,6 +146,9 @@ interface ApiConfig {
88
146
  enabled?: boolean;
89
147
  baseUrl?: string;
90
148
  authToken?: string;
149
+ tenantId?: string;
150
+ complianceGroup?: string;
151
+ trustDomain?: boolean;
91
152
  }
92
153
  interface ThemeConfig {
93
154
  bgColor?: string;
@@ -109,35 +170,52 @@ interface ThemeConfig {
109
170
  accentTextColor?: string;
110
171
  }
111
172
  interface CoreConfig {
112
- profileId?: number;
173
+ tenantId?: string;
113
174
  autoHonorGPC?: boolean | 'strict';
114
175
  locale?: string;
115
176
  disableCssTemplate?: boolean;
116
- signCookies?: boolean;
117
177
  cookieSigningKey?: string;
118
178
  allowReceipt?: boolean;
119
179
  cookieDomains?: string;
120
180
  storage?: 'cookie' | 'localStorage';
121
181
  theme?: ThemeConfig;
122
- privacyPolicyUrl?: string;
123
- regulation?: 'gdpr' | 'ccpa' | 'cpra' | 'dpdpa' | 'uk-gdpr' | 'lgpd' | 'pipeda' | 'popia' | 'pdpa-th' | 'appi' | 'kvkk';
124
182
  userId?: string;
183
+ usePrebuiltProfiles?: 'all' | NonEmptyArray<ComplianceGroupId>;
184
+ cacheResolvedProfiles?: boolean;
185
+ console?: Array<'info' | 'log' | 'warning' | 'error'>;
125
186
  }
126
187
  interface ConsentiConfig {
127
188
  core: CoreConfig;
189
+ compliance?: ComplianceWidgetConfig;
128
190
  rootEl?: string | HTMLElement;
129
191
  darkMode?: boolean;
130
192
  autoInit?: boolean;
131
193
  api?: ApiConfig;
132
194
  utils?: UtilsConfig;
133
195
  plugins?: ConsentiPlugin[];
134
- profileOverride?: Partial<ResolvedProfile>;
196
+ profileOverride?: DeepPartial<ResolvedProfile>;
197
+ /** When true, suppresses the "Powered by Consenti" footer link in the banner and modal. */
198
+ hidePoweredBy?: boolean;
135
199
  }
200
+ type ConsentiEventName = 'bannerInitialized' | 'consenti:bannerInitialized' | 'bannerVisibility' | 'consenti:bannerVisibility' | 'modalVisibility' | 'consenti:modalVisibility' | 'consentBeingSubmitted' | 'consenti:consentBeingSubmitted' | 'consentSubmitted' | 'consenti:consentSubmitted';
136
201
  interface ConsentiWidgetAPI {
137
202
  hasConsent(): boolean;
138
203
  getConsent(): ConsentValue | null;
139
204
  getConsentDate(): Date | false;
140
205
  getGTMConsent(): Record<string, string> | null;
206
+ isCookieGranted(cookieId: string, requestValue?: boolean): boolean | ConsentStatus;
207
+ isCategoryGranted(categoryId: string, requestValue?: boolean): boolean | {
208
+ [cookieId: string]: ConsentStatus;
209
+ }[];
210
+ grantAll(onlyMandatory?: boolean): Promise<void>;
211
+ denyAll(includingMandatory?: boolean): Promise<void>;
212
+ on(event: ConsentiEventName, handler: (data: ConsentEvent) => void): void;
213
+ off(event: ConsentiEventName, handler: (data: ConsentEvent) => void): void;
214
+ version(): {
215
+ package: string;
216
+ profileVersion: number | null;
217
+ consentVersion: number;
218
+ };
141
219
  bannerVisibility(): 'main' | 'gpc' | false;
142
220
  modalVisibility(): 'preference' | false;
143
221
  getProfile(): ResolvedProfile | null;
@@ -152,6 +230,10 @@ interface ConsentiWidgetAPI {
152
230
  getBannerElement(): HTMLElement | null;
153
231
  getModalElement(): HTMLElement | null;
154
232
  switchLocale(locale: string): void;
233
+ setDarkMode(enable?: boolean): void;
234
+ setTheme(theme: Partial<ThemeConfig>): void;
235
+ setConfig(config: DeepPartial<ConsentiConfig>): void;
236
+ setProfile(override: DeepPartial<ResolvedProfile>): void;
155
237
  init(): Promise<void>;
156
238
  onReady(callback: () => void): void;
157
239
  destroy(): void;
@@ -165,6 +247,19 @@ declare abstract class ConsentiPlugin {
165
247
  onModalShow?(): void | Promise<void>;
166
248
  onModalHide?(): void | Promise<void>;
167
249
  }
250
+ interface ConsentEvent {
251
+ profileId?: number;
252
+ consent?: ConsentValue;
253
+ hasExistingConsent?: boolean;
254
+ show?: boolean;
255
+ action?: boolean;
256
+ consentAction?: ConsentAction;
257
+ consentId?: string;
258
+ pageUrl?: string;
259
+ apiResponse?: unknown;
260
+ fromBroadcast?: boolean;
261
+ gpcDetected?: boolean;
262
+ }
168
263
 
169
264
  /**
170
265
  * Main entry point for the Consenti widget.
@@ -176,7 +271,7 @@ declare abstract class ConsentiPlugin {
176
271
  * @example
177
272
  * ```ts
178
273
  * const widget = new ConsentiSetup({
179
- * core: { profileId: 0 },
274
+ * compliance: { type: 'opt-in' },
180
275
  * })
181
276
  * await widget.ready
182
277
  * console.log(widget.hasConsent())
@@ -188,6 +283,7 @@ declare class ConsentiSetup implements ConsentiWidgetAPI {
188
283
  private initialized;
189
284
  private initializing;
190
285
  private profile;
286
+ private resolvedBase;
191
287
  private consentStore;
192
288
  private eventBus;
193
289
  private banner;
@@ -201,6 +297,8 @@ declare class ConsentiSetup implements ConsentiWidgetAPI {
201
297
  private lastSignature;
202
298
  /** Tracks which banner variant is currently visible, or null when hidden. */
203
299
  private _bannerVariant;
300
+ /** Maps original handler functions to their DOM-wrapped listeners for on/off bookkeeping. */
301
+ private _eventHandlers;
204
302
  /**
205
303
  * Creates and initialises the consent widget.
206
304
  *
@@ -233,6 +331,8 @@ declare class ConsentiSetup implements ConsentiWidgetAPI {
233
331
  * @param invoke - Function that calls the desired hook method on a single plugin.
234
332
  */
235
333
  private callPluginHook;
334
+ private _buildGrantAllConsent;
335
+ private _buildDenyAllConsent;
236
336
  private buildHandlers;
237
337
  /**
238
338
  * Classifies a consent submission as `accept_all`, `reject_all`, `custom`, or `update`.
@@ -381,6 +481,93 @@ declare class ConsentiSetup implements ConsentiWidgetAPI {
381
481
  * @param locale - BCP 47 locale code to switch to (e.g. `'fr'`, `'de-AT'`).
382
482
  */
383
483
  switchLocale(locale: string): void;
484
+ /**
485
+ * Returns `true` if the named cookie's consent is `'granted'`.
486
+ * When `requestValue` is `true`, returns the raw `ConsentStatus` string instead of a boolean,
487
+ * or `false` if the cookie is not in the consent map.
488
+ */
489
+ isCookieGranted(cookieId: string, requestValue?: boolean): boolean | 'granted' | 'denied' | 'objected';
490
+ /**
491
+ * Checks consent for all cookies belonging to the given category.
492
+ *
493
+ * With `requestValue = false` (default): returns `true` only when every cookie in the
494
+ * category is `'granted'`, `false` otherwise.
495
+ *
496
+ * With `requestValue = true`: returns an array of one-key records — one per cookie in the
497
+ * category — e.g. `[{ analytics: 'granted' }, { pixel: 'denied' }]`.
498
+ *
499
+ * Returns `false` / `[]` if the category is not found or the widget is not yet initialized.
500
+ */
501
+ isCategoryGranted(categoryId: string, requestValue?: boolean): boolean | {
502
+ [cookieId: string]: 'granted' | 'denied' | 'objected';
503
+ }[];
504
+ /**
505
+ * Programmatically accepts cookies and hides the banner.
506
+ *
507
+ * @param onlyMandatory - When `true`, only mandatory cookies are granted and all others
508
+ * are set to `'denied'`. Defaults to `false` (grant everything).
509
+ */
510
+ grantAll(onlyMandatory?: boolean): Promise<void>;
511
+ /**
512
+ * Programmatically denies cookies and hides the banner.
513
+ *
514
+ * @param includingMandatory - When `true`, mandatory cookies are also denied. Defaults to
515
+ * `false` (mandatory cookies remain `'granted'`). Passing `true`
516
+ * logs a warning — ensure this is intentional.
517
+ */
518
+ denyAll(includingMandatory?: boolean): Promise<void>;
519
+ /**
520
+ * Subscribes to a Consenti widget event. The `consenti:` prefix is optional —
521
+ * both `'consentSubmitted'` and `'consenti:consentSubmitted'` are accepted.
522
+ *
523
+ * The same handler reference must be passed to `off()` to unsubscribe.
524
+ *
525
+ * @example
526
+ * ```ts
527
+ * const handler = (data) => console.log(data)
528
+ * widget.on('consentSubmitted', handler)
529
+ * // later:
530
+ * widget.off('consentSubmitted', handler)
531
+ * ```
532
+ */
533
+ on(event: ConsentiEventName, handler: (data: ConsentEvent) => void): void;
534
+ /**
535
+ * Removes a previously registered event handler. The same function reference passed to
536
+ * `on()` must be used here.
537
+ */
538
+ off(event: ConsentiEventName, handler: (data: ConsentEvent) => void): void;
539
+ /**
540
+ * Returns the current version of the package and the active profile/consent versions.
541
+ * Useful for debugging and support diagnostics.
542
+ */
543
+ version(): {
544
+ package: string;
545
+ profileVersion: number | null;
546
+ consentVersion: number;
547
+ };
548
+ /**
549
+ * Toggles or sets dark mode at runtime without re-initialising the widget.
550
+ * When `enable` is omitted, the current dark mode state is toggled.
551
+ */
552
+ setDarkMode(enable?: boolean): void;
553
+ /**
554
+ * Applies partial theme token overrides at runtime, merging them into the current theme.
555
+ * CSS custom properties on the root element are updated immediately.
556
+ */
557
+ setTheme(theme: Partial<ThemeConfig>): void;
558
+ /**
559
+ * Deep-merges a partial config into the current widget config.
560
+ * Theme and dark mode side-effects are re-applied immediately.
561
+ * Changes to locale or profile require an explicit `switchLocale()` or `init()` call.
562
+ */
563
+ setConfig(config: DeepPartial<ConsentiConfig>): void;
564
+ /**
565
+ * Merges a partial profile override into the current profile and re-renders any visible UI.
566
+ * Does not make a network request — the resolved base profile from the last `init()` is reused.
567
+ *
568
+ * No-op before `init()` completes (while `resolvedBase` is null).
569
+ */
570
+ setProfile(override: DeepPartial<ResolvedProfile>): void;
384
571
  /**
385
572
  * Destroys the widget — removes all DOM elements, disconnects the BroadcastChannel,
386
573
  * and calls `destroy()` on all registered plugins.
@@ -398,7 +585,7 @@ declare class ConsentiSetup implements ConsentiWidgetAPI {
398
585
  *
399
586
  * Intended for **Next.js** (App Router and Pages Router) and any other React 18+
400
587
  * project where you manage the `ConsentiSetup` instance yourself and want the
401
- * `useConsent()` hook to re-render when consent changes.
588
+ * `useConsent()` hook to re-render when consent or UI state changes.
402
589
  *
403
590
  * @example
404
591
  * ```tsx
@@ -410,7 +597,9 @@ declare class ConsentiSetup implements ConsentiWidgetAPI {
410
597
  *
411
598
  * export function ConsentiProvider({ children }: { children: React.ReactNode }) {
412
599
  * useEffect(() => {
413
- * const widget = new ConsentiSetup({ core: { profileId: 0 } })
600
+ * const widget = new ConsentiSetup({
601
+ * api: { enabled: true, baseUrl: process.env.NEXT_PUBLIC_API_URL },
602
+ * })
414
603
  * setConsentiWidget(widget)
415
604
  * }, [])
416
605
  * return <>{children}</>
@@ -419,10 +608,10 @@ declare class ConsentiSetup implements ConsentiWidgetAPI {
419
608
  * // Any component
420
609
  * import { useConsent } from '@consenti/ui/react'
421
610
  *
422
- * function CookieBanner() {
423
- * const { hasConsent, submitConsent } = useConsent()
424
- * if (hasConsent()) return null
425
- * return <button onClick={() => submitConsent({ analytics: 'granted' })}>Accept</button>
611
+ * function CookieStatus() {
612
+ * const { hasConsent, consent, showModal } = useConsent()
613
+ * if (!hasConsent) return <button onClick={showModal}>Manage Cookies</button>
614
+ * return <span>{consent?.analytics === 'granted' ? 'Analytics on' : 'Analytics off'}</span>
426
615
  * }
427
616
  * ```
428
617
  */
@@ -435,18 +624,33 @@ declare class ConsentiSetup implements ConsentiWidgetAPI {
435
624
  */
436
625
  declare function setConsentiWidget(widget: ConsentiSetup): void;
437
626
  /**
438
- * React hook that exposes consent state and lets components react to consent changes.
627
+ * React hook that exposes consent state, banner/modal visibility, and widget actions.
628
+ *
629
+ * Subscribes to `consenti:consentSubmitted`, `consenti:bannerVisibility`, and
630
+ * `consenti:modalVisibility` on `window` and triggers re-renders on each event.
631
+ * Returns SSR-safe initial values during server render.
439
632
  *
440
- * Subscribes to `consenti:consentSubmitted` on `window` and triggers a re-render
441
- * each time consent is updated. Returns SSR-safe fallback values during server render.
633
+ * Destructure only what you need — unused values do not cause extra renders.
442
634
  *
443
- * @returns An object with `hasConsent`, `getConsent`, `submitConsent`, and `getConsentDate`.
635
+ * @returns Consent state values and widget action functions.
444
636
  */
445
637
  declare function useConsent(): {
446
- hasConsent: () => boolean;
447
- getConsent: () => ConsentValue | null;
448
- submitConsent: (_consent: Partial<ConsentValue>) => Promise<void>;
449
- getConsentDate: () => Date | false;
638
+ hasConsent: boolean;
639
+ consent: ConsentValue | null;
640
+ consentDate: Date | false;
641
+ bannerVisible: "main" | "gpc" | false;
642
+ modalVisible: "preference" | false;
643
+ showBanner: (_gpc?: boolean) => void;
644
+ hideBanner: () => void;
645
+ showModal: () => void;
646
+ hideModal: () => void;
647
+ grantAll: (_onlyMandatory?: boolean) => Promise<void>;
648
+ denyAll: (_includingMandatory?: boolean) => Promise<void>;
649
+ submitConsent: (_c: Partial<ConsentValue>) => Promise<void>;
650
+ reConsent: () => Promise<void>;
651
+ isCookieGranted: (_id: string) => boolean;
652
+ isCategoryGranted: (_id: string) => boolean;
653
+ switchLocale: (_locale: string) => void;
450
654
  };
451
655
 
452
656
  export { setConsentiWidget, useConsent };
package/dist/react.js CHANGED
@@ -1,61 +1,2 @@
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/react.ts
21
- var react_exports = {};
22
- __export(react_exports, {
23
- setConsentiWidget: () => setConsentiWidget,
24
- useConsent: () => useConsent
25
- });
26
- module.exports = __toCommonJS(react_exports);
27
- var import_react = require("react");
28
- var _widget = null;
29
- function setConsentiWidget(widget) {
30
- _widget = widget;
31
- }
32
- function useConsent() {
33
- const isServer = typeof window === "undefined";
34
- const [, forceUpdate] = (0, import_react.useState)(0);
35
- (0, import_react.useEffect)(() => {
36
- if (!_widget) return;
37
- const handler = () => forceUpdate((n) => n + 1);
38
- window.addEventListener("consenti:consentSubmitted", handler);
39
- return () => window.removeEventListener("consenti:consentSubmitted", handler);
40
- }, []);
41
- if (isServer) {
42
- return {
43
- hasConsent: () => false,
44
- getConsent: () => null,
45
- submitConsent: async (_consent) => {
46
- },
47
- getConsentDate: () => false
48
- };
49
- }
50
- return {
51
- /** Returns `true` if the visitor has saved consent. */
52
- hasConsent: () => _widget?.hasConsent() ?? false,
53
- /** Returns the stored consent map, or `null`. */
54
- getConsent: () => _widget?.getConsent() ?? null,
55
- /** Saves the given partial consent map. Missing keys default to `'denied'`. */
56
- submitConsent: (consent) => _widget?.submitConsent(consent) ?? Promise.resolve(),
57
- /** Returns the `Date` of last consent submission, or `false`. */
58
- getConsentDate: () => _widget?.getConsentDate() ?? false
59
- };
60
- }
1
+ "use strict";var r=Object.defineProperty;var g=Object.getOwnPropertyDescriptor;var h=Object.getOwnPropertyNames;var v=Object.prototype.hasOwnProperty;var y=(s,o)=>{for(var i in o)r(s,i,{get:o[i],enumerable:!0})},p=(s,o,i,l)=>{if(o&&typeof o=="object"||typeof o=="function")for(let a of h(o))!v.call(s,a)&&a!==i&&r(s,a,{get:()=>o[a],enumerable:!(l=g(o,a))||l.enumerable});return s};var V=s=>p(r({},"__esModule",{value:!0}),s);var L={};y(L,{setConsentiWidget:()=>M,useConsent:()=>P});module.exports=V(L);var t=require("react"),n=null;function M(s){n=s}function P(){let s=typeof window>"u",[o,i]=(0,t.useState)(()=>s?!1:n?.hasConsent()??!1),[l,a]=(0,t.useState)(()=>s?null:n?.getConsent()??null),[f,b]=(0,t.useState)(()=>s?!1:n?.getConsentDate()??!1),[C,u]=(0,t.useState)(!1),[m,w]=(0,t.useState)(!1);return(0,t.useEffect)(()=>{if(!n)return;let e=()=>{i(n?.hasConsent()??!1),a(n?.getConsent()??null),b(n?.getConsentDate()??!1)},d=()=>u(n?.bannerVisibility()??!1),c=()=>w(n?.modalVisibility()??!1);return window.addEventListener("consenti:consentSubmitted",e),window.addEventListener("consenti:bannerVisibility",d),window.addEventListener("consenti:modalVisibility",c),()=>{window.removeEventListener("consenti:consentSubmitted",e),window.removeEventListener("consenti:bannerVisibility",d),window.removeEventListener("consenti:modalVisibility",c)}},[]),s?{hasConsent:!1,consent:null,consentDate:!1,bannerVisible:!1,modalVisible:!1,showBanner:e=>{},hideBanner:()=>{},showModal:()=>{},hideModal:()=>{},grantAll:e=>Promise.resolve(),denyAll:e=>Promise.resolve(),submitConsent:e=>Promise.resolve(),reConsent:()=>Promise.resolve(),isCookieGranted:e=>!1,isCategoryGranted:e=>!1,switchLocale:e=>{}}:{hasConsent:o,consent:l,consentDate:f,bannerVisible:C,modalVisible:m,showBanner:e=>n?.showBanner(e),hideBanner:()=>n?.hideBanner(),showModal:()=>n?.showModal(),hideModal:()=>n?.hideModal(),grantAll:e=>n?.grantAll(e)??Promise.resolve(),denyAll:e=>n?.denyAll(e)??Promise.resolve(),submitConsent:e=>n?.submitConsent(e)??Promise.resolve(),reConsent:()=>n?.reConsent()??Promise.resolve(),isCookieGranted:e=>n?.isCookieGranted(e)??!1,isCategoryGranted:e=>n?.isCategoryGranted(e)??!1,switchLocale:e=>n?.switchLocale(e)}}
61
2
  //# sourceMappingURL=react.js.map
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react.ts"],"sourcesContent":["/**\n * React integration for `@consenti/ui`.\n *\n * Import from the `@consenti/ui/react` subpath to keep this module\n * tree-shaken out of vanilla JS or Vue bundles.\n *\n * Intended for **Next.js** (App Router and Pages Router) and any other React 18+\n * project where you manage the `ConsentiSetup` instance yourself and want the\n * `useConsent()` hook to re-render when consent changes.\n *\n * @example\n * ```tsx\n * // app/providers.tsx (Next.js App Router)\n * 'use client'\n * import { useEffect } from 'react'\n * import { ConsentiSetup } from '@consenti/ui'\n * import { setConsentiWidget } from '@consenti/ui/react'\n *\n * export function ConsentiProvider({ children }: { children: React.ReactNode }) {\n * useEffect(() => {\n * const widget = new ConsentiSetup({ core: { profileId: 0 } })\n * setConsentiWidget(widget)\n * }, [])\n * return <>{children}</>\n * }\n *\n * // Any component\n * import { useConsent } from '@consenti/ui/react'\n *\n * function CookieBanner() {\n * const { hasConsent, submitConsent } = useConsent()\n * if (hasConsent()) return null\n * return <button onClick={() => submitConsent({ analytics: 'granted' })}>Accept</button>\n * }\n * ```\n */\n\nimport { useState, useEffect } from 'react'\nimport type { ConsentValue } from './types'\nimport type { ConsentiSetup } from './core/consenti-setup'\n\n/**\n * Module-level singleton — the active `ConsentiSetup` instance.\n * Using a module-level variable (rather than React context) keeps the integration\n * zero-configuration: no `<ConsentiProvider>` wrapper is required.\n */\nlet _widget: ConsentiSetup | null = null\n\n/**\n * Registers the active `ConsentiSetup` instance so `useConsent()` can read from it.\n * Call this once after creating your widget, typically inside a `useEffect`.\n *\n * @param widget - The `ConsentiSetup` instance to register.\n */\nexport function setConsentiWidget(widget: ConsentiSetup): void {\n _widget = widget\n}\n\n/**\n * React hook that exposes consent state and lets components react to consent changes.\n *\n * Subscribes to `consenti:consentSubmitted` on `window` and triggers a re-render\n * each time consent is updated. Returns SSR-safe fallback values during server render.\n *\n * @returns An object with `hasConsent`, `getConsent`, `submitConsent`, and `getConsentDate`.\n */\nexport function useConsent() {\n const isServer = typeof window === 'undefined'\n\n const [, forceUpdate] = useState(0)\n\n useEffect(() => {\n if (!_widget) return\n const handler = () => forceUpdate((n) => n + 1)\n window.addEventListener('consenti:consentSubmitted', handler)\n return () => window.removeEventListener('consenti:consentSubmitted', handler)\n }, [])\n\n if (isServer) {\n return {\n hasConsent: () => false as boolean,\n getConsent: () => null as ConsentValue | null,\n submitConsent: async (_consent: Partial<ConsentValue>) => {},\n getConsentDate: () => false as Date | false,\n }\n }\n\n return {\n /** Returns `true` if the visitor has saved consent. */\n hasConsent: () => _widget?.hasConsent() ?? false,\n /** Returns the stored consent map, or `null`. */\n getConsent: () => _widget?.getConsent() ?? null,\n /** Saves the given partial consent map. Missing keys default to `'denied'`. */\n submitConsent: (consent: Partial<ConsentValue>) =>\n _widget?.submitConsent(consent) ?? Promise.resolve(),\n /** Returns the `Date` of last consent submission, or `false`. */\n getConsentDate: () => _widget?.getConsentDate() ?? false,\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqCA,mBAAoC;AASpC,IAAI,UAAgC;AAQ7B,SAAS,kBAAkB,QAA6B;AAC7D,YAAU;AACZ;AAUO,SAAS,aAAa;AAC3B,QAAM,WAAW,OAAO,WAAW;AAEnC,QAAM,CAAC,EAAE,WAAW,QAAI,uBAAS,CAAC;AAElC,8BAAU,MAAM;AACd,QAAI,CAAC,QAAS;AACd,UAAM,UAAU,MAAM,YAAY,CAAC,MAAM,IAAI,CAAC;AAC9C,WAAO,iBAAiB,6BAA6B,OAAO;AAC5D,WAAO,MAAM,OAAO,oBAAoB,6BAA6B,OAAO;AAAA,EAC9E,GAAG,CAAC,CAAC;AAEL,MAAI,UAAU;AACZ,WAAO;AAAA,MACL,YAAY,MAAM;AAAA,MAClB,YAAY,MAAM;AAAA,MAClB,eAAe,OAAO,aAAoC;AAAA,MAAC;AAAA,MAC3D,gBAAgB,MAAM;AAAA,IACxB;AAAA,EACF;AAEA,SAAO;AAAA;AAAA,IAEL,YAAY,MAAM,SAAS,WAAW,KAAK;AAAA;AAAA,IAE3C,YAAY,MAAM,SAAS,WAAW,KAAK;AAAA;AAAA,IAE3C,eAAe,CAAC,YACd,SAAS,cAAc,OAAO,KAAK,QAAQ,QAAQ;AAAA;AAAA,IAErD,gBAAgB,MAAM,SAAS,eAAe,KAAK;AAAA,EACrD;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/react.ts"],"sourcesContent":["/**\n * React integration for `@consenti/ui`.\n *\n * Import from the `@consenti/ui/react` subpath to keep this module\n * tree-shaken out of vanilla JS or Vue bundles.\n *\n * Intended for **Next.js** (App Router and Pages Router) and any other React 18+\n * project where you manage the `ConsentiSetup` instance yourself and want the\n * `useConsent()` hook to re-render when consent or UI state changes.\n *\n * @example\n * ```tsx\n * // app/providers.tsx (Next.js App Router)\n * 'use client'\n * import { useEffect } from 'react'\n * import { ConsentiSetup } from '@consenti/ui'\n * import { setConsentiWidget } from '@consenti/ui/react'\n *\n * export function ConsentiProvider({ children }: { children: React.ReactNode }) {\n * useEffect(() => {\n * const widget = new ConsentiSetup({\n * api: { enabled: true, baseUrl: process.env.NEXT_PUBLIC_API_URL },\n * })\n * setConsentiWidget(widget)\n * }, [])\n * return <>{children}</>\n * }\n *\n * // Any component\n * import { useConsent } from '@consenti/ui/react'\n *\n * function CookieStatus() {\n * const { hasConsent, consent, showModal } = useConsent()\n * if (!hasConsent) return <button onClick={showModal}>Manage Cookies</button>\n * return <span>{consent?.analytics === 'granted' ? 'Analytics on' : 'Analytics off'}</span>\n * }\n * ```\n */\n\nimport { useState, useEffect } from 'react'\nimport type { ConsentValue } from './types'\nimport type { ConsentiSetup } from './core/consenti-setup'\n\n/**\n * Module-level singleton — the active `ConsentiSetup` instance.\n * Using a module-level variable (rather than React context) keeps the integration\n * zero-configuration: no `<ConsentiProvider>` wrapper is required.\n */\nlet _widget: ConsentiSetup | null = null\n\n/**\n * Registers the active `ConsentiSetup` instance so `useConsent()` can read from it.\n * Call this once after creating your widget, typically inside a `useEffect`.\n *\n * @param widget - The `ConsentiSetup` instance to register.\n */\nexport function setConsentiWidget(widget: ConsentiSetup): void {\n _widget = widget\n}\n\n/**\n * React hook that exposes consent state, banner/modal visibility, and widget actions.\n *\n * Subscribes to `consenti:consentSubmitted`, `consenti:bannerVisibility`, and\n * `consenti:modalVisibility` on `window` and triggers re-renders on each event.\n * Returns SSR-safe initial values during server render.\n *\n * Destructure only what you need — unused values do not cause extra renders.\n *\n * @returns Consent state values and widget action functions.\n */\nexport function useConsent() {\n const isServer = typeof window === 'undefined'\n\n const [hasConsent, setHasConsent] = useState<boolean>(() =>\n !isServer ? (_widget?.hasConsent() ?? false) : false,\n )\n const [consent, setConsent] = useState<ConsentValue | null>(() =>\n !isServer ? (_widget?.getConsent() ?? null) : null,\n )\n const [consentDate, setConsentDate] = useState<Date | false>(() =>\n !isServer ? (_widget?.getConsentDate() ?? false) : false,\n )\n const [bannerVisible, setBannerVisible] = useState<'main' | 'gpc' | false>(false)\n const [modalVisible, setModalVisible] = useState<'preference' | false>(false)\n\n useEffect(() => {\n if (!_widget) return\n\n const onConsent = () => {\n setHasConsent(_widget?.hasConsent() ?? false)\n setConsent(_widget?.getConsent() ?? null)\n setConsentDate(_widget?.getConsentDate() ?? false)\n }\n const onBanner = () => setBannerVisible(_widget?.bannerVisibility() ?? false)\n const onModal = () => setModalVisible(_widget?.modalVisibility() ?? false)\n\n window.addEventListener('consenti:consentSubmitted', onConsent)\n window.addEventListener('consenti:bannerVisibility', onBanner)\n window.addEventListener('consenti:modalVisibility', onModal)\n\n return () => {\n window.removeEventListener('consenti:consentSubmitted', onConsent)\n window.removeEventListener('consenti:bannerVisibility', onBanner)\n window.removeEventListener('consenti:modalVisibility', onModal)\n }\n }, [])\n\n // ── SSR-safe fallback ──────────────────────────────────────────────────────\n if (isServer) {\n return {\n hasConsent: false as boolean,\n consent: null as ConsentValue | null,\n consentDate: false as Date | false,\n bannerVisible: false as 'main' | 'gpc' | false,\n modalVisible: false as 'preference' | false,\n showBanner: (_gpc?: boolean) => {},\n hideBanner: () => {},\n showModal: () => {},\n hideModal: () => {},\n grantAll: (_onlyMandatory?: boolean) => Promise.resolve(),\n denyAll: (_includingMandatory?: boolean) => Promise.resolve(),\n submitConsent: (_c: Partial<ConsentValue>) => Promise.resolve(),\n reConsent: () => Promise.resolve(),\n isCookieGranted: (_id: string) => false as boolean,\n isCategoryGranted: (_id: string) => false as boolean,\n switchLocale: (_locale: string) => {},\n }\n }\n\n // ── Client ────────────────────────────────────────────────────────────────\n return {\n /** `true` if the visitor has saved a valid consent record. */\n hasConsent,\n /** The stored consent map (`Record<cookieId, status>`), or `null` before first submission. */\n consent,\n /** `Date` of the last consent submission, or `false` if no consent exists. */\n consentDate,\n /** Current banner state: `'main'`, `'gpc'`, or `false` (hidden). */\n bannerVisible,\n /** Current modal state: `'preference'` or `false` (hidden). */\n modalVisible,\n\n /** Shows the consent banner. Pass `true` to force the GPC variant. */\n showBanner: (gpc?: boolean) => _widget?.showBanner(gpc),\n /** Hides the consent banner. */\n hideBanner: () => _widget?.hideBanner(),\n /** Opens the preference modal. */\n showModal: () => _widget?.showModal(),\n /** Closes the preference modal. */\n hideModal: () => _widget?.hideModal(),\n\n /** Grants all non-mandatory cookies. Pass `true` to grant only mandatory ones and deny the rest. */\n grantAll: (onlyMandatory?: boolean) => _widget?.grantAll(onlyMandatory) ?? Promise.resolve(),\n /** Denies all non-mandatory cookies. Pass `true` to deny mandatory ones too (use with care). */\n denyAll: (includingMandatory?: boolean) => _widget?.denyAll(includingMandatory) ?? Promise.resolve(),\n /** Saves the given partial consent map. */\n submitConsent: (c: Partial<ConsentValue>) => _widget?.submitConsent(c) ?? Promise.resolve(),\n /** Deletes the current consent record and re-shows the banner. */\n reConsent: () => _widget?.reConsent() ?? Promise.resolve(),\n\n /** Returns `true` if the given cookie ID is `'granted'`. */\n isCookieGranted: (id: string) => (_widget?.isCookieGranted(id) ?? false) as boolean,\n /** Returns `true` if every cookie in the given category is `'granted'`. */\n isCategoryGranted: (id: string) => (_widget?.isCategoryGranted(id) ?? false) as boolean,\n /** Switches the active locale and re-renders the banner and modal. */\n switchLocale: (locale: string) => _widget?.switchLocale(locale),\n }\n}\n"],"mappings":"yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,uBAAAE,EAAA,eAAAC,IAAA,eAAAC,EAAAJ,GAuCA,IAAAK,EAAoC,iBAShCC,EAAgC,KAQ7B,SAASJ,EAAkBK,EAA6B,CAC7DD,EAAUC,CACZ,CAaO,SAASJ,GAAa,CAC3B,IAAMK,EAAW,OAAO,OAAW,IAE7B,CAACC,EAAYC,CAAa,KAAI,YAAkB,IACnDF,EAA8C,GAAlCF,GAAS,WAAW,GAAK,EACxC,EACM,CAACK,EAASC,CAAU,KAAI,YAA8B,IACzDJ,EAA6C,KAAjCF,GAAS,WAAW,GAAK,IACxC,EACM,CAACO,EAAaC,CAAc,KAAI,YAAuB,IAC1DN,EAAkD,GAAtCF,GAAS,eAAe,GAAK,EAC5C,EACM,CAACS,EAAeC,CAAgB,KAAI,YAAiC,EAAK,EAC1E,CAACC,EAAcC,CAAe,KAAI,YAA+B,EAAK,EAyB5E,SAvBA,aAAU,IAAM,CACd,GAAI,CAACZ,EAAS,OAEd,IAAMa,EAAY,IAAM,CACtBT,EAAcJ,GAAS,WAAW,GAAK,EAAK,EAC5CM,EAAWN,GAAS,WAAW,GAAK,IAAI,EACxCQ,EAAeR,GAAS,eAAe,GAAK,EAAK,CACnD,EACMc,EAAW,IAAMJ,EAAiBV,GAAS,iBAAiB,GAAK,EAAK,EACtEe,EAAW,IAAMH,EAAgBZ,GAAS,gBAAgB,GAAK,EAAK,EAE1E,cAAO,iBAAiB,4BAA6Ba,CAAS,EAC9D,OAAO,iBAAiB,4BAA8BC,CAAQ,EAC9D,OAAO,iBAAiB,2BAA8BC,CAAO,EAEtD,IAAM,CACX,OAAO,oBAAoB,4BAA6BF,CAAS,EACjE,OAAO,oBAAoB,4BAA8BC,CAAQ,EACjE,OAAO,oBAAoB,2BAA8BC,CAAO,CAClE,CACF,EAAG,CAAC,CAAC,EAGDb,EACK,CACL,WAAc,GACd,QAAc,KACd,YAAc,GACd,cAAe,GACf,aAAe,GACf,WAAec,GAAmB,CAAC,EACnC,WAAc,IAAM,CAAC,EACrB,UAAc,IAAM,CAAC,EACrB,UAAc,IAAM,CAAC,EACrB,SAAeC,GAA6B,QAAQ,QAAQ,EAC5D,QAAeC,GAAkC,QAAQ,QAAQ,EACjE,cAAgBC,GAA8B,QAAQ,QAAQ,EAC9D,UAAc,IAAM,QAAQ,QAAQ,EACpC,gBAAoBC,GAAgB,GACpC,kBAAoBA,GAAgB,GACpC,aAAeC,GAAoB,CAAC,CACtC,EAIK,CAEL,WAAAlB,EAEA,QAAAE,EAEA,YAAAE,EAEA,cAAAE,EAEA,aAAAE,EAGA,WAAaW,GAAkBtB,GAAS,WAAWsB,CAAG,EAEtD,WAAY,IAAMtB,GAAS,WAAW,EAEtC,UAAW,IAAMA,GAAS,UAAU,EAEpC,UAAW,IAAMA,GAAS,UAAU,EAGpC,SAAWuB,GAA4BvB,GAAS,SAASuB,CAAa,GAAK,QAAQ,QAAQ,EAE3F,QAAUC,GAAiCxB,GAAS,QAAQwB,CAAkB,GAAK,QAAQ,QAAQ,EAEnG,cAAgBC,GAA6BzB,GAAS,cAAcyB,CAAC,GAAK,QAAQ,QAAQ,EAE1F,UAAW,IAAMzB,GAAS,UAAU,GAAK,QAAQ,QAAQ,EAGzD,gBAAkB0B,GAAgB1B,GAAS,gBAAgB0B,CAAE,GAAK,GAElE,kBAAoBA,GAAgB1B,GAAS,kBAAkB0B,CAAE,GAAK,GAEtE,aAAeC,GAAmB3B,GAAS,aAAa2B,CAAM,CAChE,CACF","names":["react_exports","__export","setConsentiWidget","useConsent","__toCommonJS","import_react","_widget","widget","isServer","hasConsent","setHasConsent","consent","setConsent","consentDate","setConsentDate","bannerVisible","setBannerVisible","modalVisible","setModalVisible","onConsent","onBanner","onModal","_gpc","_onlyMandatory","_includingMandatory","_c","_id","_locale","gpc","onlyMandatory","includingMandatory","c","id","locale"]}
package/dist/react.mjs CHANGED
@@ -1,40 +1,2 @@
1
- // src/react.ts
2
- import { useState, useEffect } from "react";
3
- var _widget = null;
4
- function setConsentiWidget(widget) {
5
- _widget = widget;
6
- }
7
- function useConsent() {
8
- const isServer = typeof window === "undefined";
9
- const [, forceUpdate] = useState(0);
10
- useEffect(() => {
11
- if (!_widget) return;
12
- const handler = () => forceUpdate((n) => n + 1);
13
- window.addEventListener("consenti:consentSubmitted", handler);
14
- return () => window.removeEventListener("consenti:consentSubmitted", handler);
15
- }, []);
16
- if (isServer) {
17
- return {
18
- hasConsent: () => false,
19
- getConsent: () => null,
20
- submitConsent: async (_consent) => {
21
- },
22
- getConsentDate: () => false
23
- };
24
- }
25
- return {
26
- /** Returns `true` if the visitor has saved consent. */
27
- hasConsent: () => _widget?.hasConsent() ?? false,
28
- /** Returns the stored consent map, or `null`. */
29
- getConsent: () => _widget?.getConsent() ?? null,
30
- /** Saves the given partial consent map. Missing keys default to `'denied'`. */
31
- submitConsent: (consent) => _widget?.submitConsent(consent) ?? Promise.resolve(),
32
- /** Returns the `Date` of last consent submission, or `false`. */
33
- getConsentDate: () => _widget?.getConsentDate() ?? false
34
- };
35
- }
36
- export {
37
- setConsentiWidget,
38
- useConsent
39
- };
1
+ import{useState as o,useEffect as w}from"react";var n=null;function h(s){n=s}function v(){let s=typeof window>"u",[i,l]=o(()=>s?!1:n?.hasConsent()??!1),[r,d]=o(()=>s?null:n?.getConsent()??null),[c,f]=o(()=>s?!1:n?.getConsentDate()??!1),[b,C]=o(!1),[u,m]=o(!1);return w(()=>{if(!n)return;let e=()=>{l(n?.hasConsent()??!1),d(n?.getConsent()??null),f(n?.getConsentDate()??!1)},t=()=>C(n?.bannerVisibility()??!1),a=()=>m(n?.modalVisibility()??!1);return window.addEventListener("consenti:consentSubmitted",e),window.addEventListener("consenti:bannerVisibility",t),window.addEventListener("consenti:modalVisibility",a),()=>{window.removeEventListener("consenti:consentSubmitted",e),window.removeEventListener("consenti:bannerVisibility",t),window.removeEventListener("consenti:modalVisibility",a)}},[]),s?{hasConsent:!1,consent:null,consentDate:!1,bannerVisible:!1,modalVisible:!1,showBanner:e=>{},hideBanner:()=>{},showModal:()=>{},hideModal:()=>{},grantAll:e=>Promise.resolve(),denyAll:e=>Promise.resolve(),submitConsent:e=>Promise.resolve(),reConsent:()=>Promise.resolve(),isCookieGranted:e=>!1,isCategoryGranted:e=>!1,switchLocale:e=>{}}:{hasConsent:i,consent:r,consentDate:c,bannerVisible:b,modalVisible:u,showBanner:e=>n?.showBanner(e),hideBanner:()=>n?.hideBanner(),showModal:()=>n?.showModal(),hideModal:()=>n?.hideModal(),grantAll:e=>n?.grantAll(e)??Promise.resolve(),denyAll:e=>n?.denyAll(e)??Promise.resolve(),submitConsent:e=>n?.submitConsent(e)??Promise.resolve(),reConsent:()=>n?.reConsent()??Promise.resolve(),isCookieGranted:e=>n?.isCookieGranted(e)??!1,isCategoryGranted:e=>n?.isCategoryGranted(e)??!1,switchLocale:e=>n?.switchLocale(e)}}export{h as setConsentiWidget,v as useConsent};
40
2
  //# sourceMappingURL=react.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react.ts"],"sourcesContent":["/**\n * React integration for `@consenti/ui`.\n *\n * Import from the `@consenti/ui/react` subpath to keep this module\n * tree-shaken out of vanilla JS or Vue bundles.\n *\n * Intended for **Next.js** (App Router and Pages Router) and any other React 18+\n * project where you manage the `ConsentiSetup` instance yourself and want the\n * `useConsent()` hook to re-render when consent changes.\n *\n * @example\n * ```tsx\n * // app/providers.tsx (Next.js App Router)\n * 'use client'\n * import { useEffect } from 'react'\n * import { ConsentiSetup } from '@consenti/ui'\n * import { setConsentiWidget } from '@consenti/ui/react'\n *\n * export function ConsentiProvider({ children }: { children: React.ReactNode }) {\n * useEffect(() => {\n * const widget = new ConsentiSetup({ core: { profileId: 0 } })\n * setConsentiWidget(widget)\n * }, [])\n * return <>{children}</>\n * }\n *\n * // Any component\n * import { useConsent } from '@consenti/ui/react'\n *\n * function CookieBanner() {\n * const { hasConsent, submitConsent } = useConsent()\n * if (hasConsent()) return null\n * return <button onClick={() => submitConsent({ analytics: 'granted' })}>Accept</button>\n * }\n * ```\n */\n\nimport { useState, useEffect } from 'react'\nimport type { ConsentValue } from './types'\nimport type { ConsentiSetup } from './core/consenti-setup'\n\n/**\n * Module-level singleton — the active `ConsentiSetup` instance.\n * Using a module-level variable (rather than React context) keeps the integration\n * zero-configuration: no `<ConsentiProvider>` wrapper is required.\n */\nlet _widget: ConsentiSetup | null = null\n\n/**\n * Registers the active `ConsentiSetup` instance so `useConsent()` can read from it.\n * Call this once after creating your widget, typically inside a `useEffect`.\n *\n * @param widget - The `ConsentiSetup` instance to register.\n */\nexport function setConsentiWidget(widget: ConsentiSetup): void {\n _widget = widget\n}\n\n/**\n * React hook that exposes consent state and lets components react to consent changes.\n *\n * Subscribes to `consenti:consentSubmitted` on `window` and triggers a re-render\n * each time consent is updated. Returns SSR-safe fallback values during server render.\n *\n * @returns An object with `hasConsent`, `getConsent`, `submitConsent`, and `getConsentDate`.\n */\nexport function useConsent() {\n const isServer = typeof window === 'undefined'\n\n const [, forceUpdate] = useState(0)\n\n useEffect(() => {\n if (!_widget) return\n const handler = () => forceUpdate((n) => n + 1)\n window.addEventListener('consenti:consentSubmitted', handler)\n return () => window.removeEventListener('consenti:consentSubmitted', handler)\n }, [])\n\n if (isServer) {\n return {\n hasConsent: () => false as boolean,\n getConsent: () => null as ConsentValue | null,\n submitConsent: async (_consent: Partial<ConsentValue>) => {},\n getConsentDate: () => false as Date | false,\n }\n }\n\n return {\n /** Returns `true` if the visitor has saved consent. */\n hasConsent: () => _widget?.hasConsent() ?? false,\n /** Returns the stored consent map, or `null`. */\n getConsent: () => _widget?.getConsent() ?? null,\n /** Saves the given partial consent map. Missing keys default to `'denied'`. */\n submitConsent: (consent: Partial<ConsentValue>) =>\n _widget?.submitConsent(consent) ?? Promise.resolve(),\n /** Returns the `Date` of last consent submission, or `false`. */\n getConsentDate: () => _widget?.getConsentDate() ?? false,\n }\n}\n"],"mappings":";AAqCA,SAAS,UAAU,iBAAiB;AASpC,IAAI,UAAgC;AAQ7B,SAAS,kBAAkB,QAA6B;AAC7D,YAAU;AACZ;AAUO,SAAS,aAAa;AAC3B,QAAM,WAAW,OAAO,WAAW;AAEnC,QAAM,CAAC,EAAE,WAAW,IAAI,SAAS,CAAC;AAElC,YAAU,MAAM;AACd,QAAI,CAAC,QAAS;AACd,UAAM,UAAU,MAAM,YAAY,CAAC,MAAM,IAAI,CAAC;AAC9C,WAAO,iBAAiB,6BAA6B,OAAO;AAC5D,WAAO,MAAM,OAAO,oBAAoB,6BAA6B,OAAO;AAAA,EAC9E,GAAG,CAAC,CAAC;AAEL,MAAI,UAAU;AACZ,WAAO;AAAA,MACL,YAAY,MAAM;AAAA,MAClB,YAAY,MAAM;AAAA,MAClB,eAAe,OAAO,aAAoC;AAAA,MAAC;AAAA,MAC3D,gBAAgB,MAAM;AAAA,IACxB;AAAA,EACF;AAEA,SAAO;AAAA;AAAA,IAEL,YAAY,MAAM,SAAS,WAAW,KAAK;AAAA;AAAA,IAE3C,YAAY,MAAM,SAAS,WAAW,KAAK;AAAA;AAAA,IAE3C,eAAe,CAAC,YACd,SAAS,cAAc,OAAO,KAAK,QAAQ,QAAQ;AAAA;AAAA,IAErD,gBAAgB,MAAM,SAAS,eAAe,KAAK;AAAA,EACrD;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/react.ts"],"sourcesContent":["/**\n * React integration for `@consenti/ui`.\n *\n * Import from the `@consenti/ui/react` subpath to keep this module\n * tree-shaken out of vanilla JS or Vue bundles.\n *\n * Intended for **Next.js** (App Router and Pages Router) and any other React 18+\n * project where you manage the `ConsentiSetup` instance yourself and want the\n * `useConsent()` hook to re-render when consent or UI state changes.\n *\n * @example\n * ```tsx\n * // app/providers.tsx (Next.js App Router)\n * 'use client'\n * import { useEffect } from 'react'\n * import { ConsentiSetup } from '@consenti/ui'\n * import { setConsentiWidget } from '@consenti/ui/react'\n *\n * export function ConsentiProvider({ children }: { children: React.ReactNode }) {\n * useEffect(() => {\n * const widget = new ConsentiSetup({\n * api: { enabled: true, baseUrl: process.env.NEXT_PUBLIC_API_URL },\n * })\n * setConsentiWidget(widget)\n * }, [])\n * return <>{children}</>\n * }\n *\n * // Any component\n * import { useConsent } from '@consenti/ui/react'\n *\n * function CookieStatus() {\n * const { hasConsent, consent, showModal } = useConsent()\n * if (!hasConsent) return <button onClick={showModal}>Manage Cookies</button>\n * return <span>{consent?.analytics === 'granted' ? 'Analytics on' : 'Analytics off'}</span>\n * }\n * ```\n */\n\nimport { useState, useEffect } from 'react'\nimport type { ConsentValue } from './types'\nimport type { ConsentiSetup } from './core/consenti-setup'\n\n/**\n * Module-level singleton — the active `ConsentiSetup` instance.\n * Using a module-level variable (rather than React context) keeps the integration\n * zero-configuration: no `<ConsentiProvider>` wrapper is required.\n */\nlet _widget: ConsentiSetup | null = null\n\n/**\n * Registers the active `ConsentiSetup` instance so `useConsent()` can read from it.\n * Call this once after creating your widget, typically inside a `useEffect`.\n *\n * @param widget - The `ConsentiSetup` instance to register.\n */\nexport function setConsentiWidget(widget: ConsentiSetup): void {\n _widget = widget\n}\n\n/**\n * React hook that exposes consent state, banner/modal visibility, and widget actions.\n *\n * Subscribes to `consenti:consentSubmitted`, `consenti:bannerVisibility`, and\n * `consenti:modalVisibility` on `window` and triggers re-renders on each event.\n * Returns SSR-safe initial values during server render.\n *\n * Destructure only what you need — unused values do not cause extra renders.\n *\n * @returns Consent state values and widget action functions.\n */\nexport function useConsent() {\n const isServer = typeof window === 'undefined'\n\n const [hasConsent, setHasConsent] = useState<boolean>(() =>\n !isServer ? (_widget?.hasConsent() ?? false) : false,\n )\n const [consent, setConsent] = useState<ConsentValue | null>(() =>\n !isServer ? (_widget?.getConsent() ?? null) : null,\n )\n const [consentDate, setConsentDate] = useState<Date | false>(() =>\n !isServer ? (_widget?.getConsentDate() ?? false) : false,\n )\n const [bannerVisible, setBannerVisible] = useState<'main' | 'gpc' | false>(false)\n const [modalVisible, setModalVisible] = useState<'preference' | false>(false)\n\n useEffect(() => {\n if (!_widget) return\n\n const onConsent = () => {\n setHasConsent(_widget?.hasConsent() ?? false)\n setConsent(_widget?.getConsent() ?? null)\n setConsentDate(_widget?.getConsentDate() ?? false)\n }\n const onBanner = () => setBannerVisible(_widget?.bannerVisibility() ?? false)\n const onModal = () => setModalVisible(_widget?.modalVisibility() ?? false)\n\n window.addEventListener('consenti:consentSubmitted', onConsent)\n window.addEventListener('consenti:bannerVisibility', onBanner)\n window.addEventListener('consenti:modalVisibility', onModal)\n\n return () => {\n window.removeEventListener('consenti:consentSubmitted', onConsent)\n window.removeEventListener('consenti:bannerVisibility', onBanner)\n window.removeEventListener('consenti:modalVisibility', onModal)\n }\n }, [])\n\n // ── SSR-safe fallback ──────────────────────────────────────────────────────\n if (isServer) {\n return {\n hasConsent: false as boolean,\n consent: null as ConsentValue | null,\n consentDate: false as Date | false,\n bannerVisible: false as 'main' | 'gpc' | false,\n modalVisible: false as 'preference' | false,\n showBanner: (_gpc?: boolean) => {},\n hideBanner: () => {},\n showModal: () => {},\n hideModal: () => {},\n grantAll: (_onlyMandatory?: boolean) => Promise.resolve(),\n denyAll: (_includingMandatory?: boolean) => Promise.resolve(),\n submitConsent: (_c: Partial<ConsentValue>) => Promise.resolve(),\n reConsent: () => Promise.resolve(),\n isCookieGranted: (_id: string) => false as boolean,\n isCategoryGranted: (_id: string) => false as boolean,\n switchLocale: (_locale: string) => {},\n }\n }\n\n // ── Client ────────────────────────────────────────────────────────────────\n return {\n /** `true` if the visitor has saved a valid consent record. */\n hasConsent,\n /** The stored consent map (`Record<cookieId, status>`), or `null` before first submission. */\n consent,\n /** `Date` of the last consent submission, or `false` if no consent exists. */\n consentDate,\n /** Current banner state: `'main'`, `'gpc'`, or `false` (hidden). */\n bannerVisible,\n /** Current modal state: `'preference'` or `false` (hidden). */\n modalVisible,\n\n /** Shows the consent banner. Pass `true` to force the GPC variant. */\n showBanner: (gpc?: boolean) => _widget?.showBanner(gpc),\n /** Hides the consent banner. */\n hideBanner: () => _widget?.hideBanner(),\n /** Opens the preference modal. */\n showModal: () => _widget?.showModal(),\n /** Closes the preference modal. */\n hideModal: () => _widget?.hideModal(),\n\n /** Grants all non-mandatory cookies. Pass `true` to grant only mandatory ones and deny the rest. */\n grantAll: (onlyMandatory?: boolean) => _widget?.grantAll(onlyMandatory) ?? Promise.resolve(),\n /** Denies all non-mandatory cookies. Pass `true` to deny mandatory ones too (use with care). */\n denyAll: (includingMandatory?: boolean) => _widget?.denyAll(includingMandatory) ?? Promise.resolve(),\n /** Saves the given partial consent map. */\n submitConsent: (c: Partial<ConsentValue>) => _widget?.submitConsent(c) ?? Promise.resolve(),\n /** Deletes the current consent record and re-shows the banner. */\n reConsent: () => _widget?.reConsent() ?? Promise.resolve(),\n\n /** Returns `true` if the given cookie ID is `'granted'`. */\n isCookieGranted: (id: string) => (_widget?.isCookieGranted(id) ?? false) as boolean,\n /** Returns `true` if every cookie in the given category is `'granted'`. */\n isCategoryGranted: (id: string) => (_widget?.isCategoryGranted(id) ?? false) as boolean,\n /** Switches the active locale and re-renders the banner and modal. */\n switchLocale: (locale: string) => _widget?.switchLocale(locale),\n }\n}\n"],"mappings":"AAuCA,OAAS,YAAAA,EAAU,aAAAC,MAAiB,QASpC,IAAIC,EAAgC,KAQ7B,SAASC,EAAkBC,EAA6B,CAC7DF,EAAUE,CACZ,CAaO,SAASC,GAAa,CAC3B,IAAMC,EAAW,OAAO,OAAW,IAE7B,CAACC,EAAYC,CAAa,EAAIR,EAAkB,IACnDM,EAA8C,GAAlCJ,GAAS,WAAW,GAAK,EACxC,EACM,CAACO,EAASC,CAAU,EAAIV,EAA8B,IACzDM,EAA6C,KAAjCJ,GAAS,WAAW,GAAK,IACxC,EACM,CAACS,EAAaC,CAAc,EAAIZ,EAAuB,IAC1DM,EAAkD,GAAtCJ,GAAS,eAAe,GAAK,EAC5C,EACM,CAACW,EAAeC,CAAgB,EAAId,EAAiC,EAAK,EAC1E,CAACe,EAAcC,CAAe,EAAIhB,EAA+B,EAAK,EAyB5E,OAvBAC,EAAU,IAAM,CACd,GAAI,CAACC,EAAS,OAEd,IAAMe,EAAY,IAAM,CACtBT,EAAcN,GAAS,WAAW,GAAK,EAAK,EAC5CQ,EAAWR,GAAS,WAAW,GAAK,IAAI,EACxCU,EAAeV,GAAS,eAAe,GAAK,EAAK,CACnD,EACMgB,EAAW,IAAMJ,EAAiBZ,GAAS,iBAAiB,GAAK,EAAK,EACtEiB,EAAW,IAAMH,EAAgBd,GAAS,gBAAgB,GAAK,EAAK,EAE1E,cAAO,iBAAiB,4BAA6Be,CAAS,EAC9D,OAAO,iBAAiB,4BAA8BC,CAAQ,EAC9D,OAAO,iBAAiB,2BAA8BC,CAAO,EAEtD,IAAM,CACX,OAAO,oBAAoB,4BAA6BF,CAAS,EACjE,OAAO,oBAAoB,4BAA8BC,CAAQ,EACjE,OAAO,oBAAoB,2BAA8BC,CAAO,CAClE,CACF,EAAG,CAAC,CAAC,EAGDb,EACK,CACL,WAAc,GACd,QAAc,KACd,YAAc,GACd,cAAe,GACf,aAAe,GACf,WAAec,GAAmB,CAAC,EACnC,WAAc,IAAM,CAAC,EACrB,UAAc,IAAM,CAAC,EACrB,UAAc,IAAM,CAAC,EACrB,SAAeC,GAA6B,QAAQ,QAAQ,EAC5D,QAAeC,GAAkC,QAAQ,QAAQ,EACjE,cAAgBC,GAA8B,QAAQ,QAAQ,EAC9D,UAAc,IAAM,QAAQ,QAAQ,EACpC,gBAAoBC,GAAgB,GACpC,kBAAoBA,GAAgB,GACpC,aAAeC,GAAoB,CAAC,CACtC,EAIK,CAEL,WAAAlB,EAEA,QAAAE,EAEA,YAAAE,EAEA,cAAAE,EAEA,aAAAE,EAGA,WAAaW,GAAkBxB,GAAS,WAAWwB,CAAG,EAEtD,WAAY,IAAMxB,GAAS,WAAW,EAEtC,UAAW,IAAMA,GAAS,UAAU,EAEpC,UAAW,IAAMA,GAAS,UAAU,EAGpC,SAAWyB,GAA4BzB,GAAS,SAASyB,CAAa,GAAK,QAAQ,QAAQ,EAE3F,QAAUC,GAAiC1B,GAAS,QAAQ0B,CAAkB,GAAK,QAAQ,QAAQ,EAEnG,cAAgBC,GAA6B3B,GAAS,cAAc2B,CAAC,GAAK,QAAQ,QAAQ,EAE1F,UAAW,IAAM3B,GAAS,UAAU,GAAK,QAAQ,QAAQ,EAGzD,gBAAkB4B,GAAgB5B,GAAS,gBAAgB4B,CAAE,GAAK,GAElE,kBAAoBA,GAAgB5B,GAAS,kBAAkB4B,CAAE,GAAK,GAEtE,aAAeC,GAAmB7B,GAAS,aAAa6B,CAAM,CAChE,CACF","names":["useState","useEffect","_widget","setConsentiWidget","widget","useConsent","isServer","hasConsent","setHasConsent","consent","setConsent","consentDate","setConsentDate","bannerVisible","setBannerVisible","modalVisible","setModalVisible","onConsent","onBanner","onModal","_gpc","_onlyMandatory","_includingMandatory","_c","_id","_locale","gpc","onlyMandatory","includingMandatory","c","id","locale"]}