@c15t/react 2.2.0-canary-20260727202135 → 2.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.
@@ -1 +1 @@
1
- "use strict";const __rslib_import_meta_url__="u"<typeof document?new(require("url".replace("",""))).URL("file:"+__filename).href:document.currentScript&&document.currentScript.src||new URL("main.js",document.baseURI).href;var __webpack_require__={};__webpack_require__.d=(e,t,r)=>{var n=(t,r)=>{for(var n in t)__webpack_require__.o(t,n)&&!__webpack_require__.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,[r]:t[n]})};n(t,"get"),n(r,"value")},__webpack_require__.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),__webpack_require__.r=e=>{"u">typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var __webpack_exports__={};__webpack_require__.r(__webpack_exports__),__webpack_require__.d(__webpack_exports__,{ConsentManagerProvider:()=>ConsentManagerProvider,clearConsentRuntimeCache:()=>clearConsentRuntimeCache});const jsx_runtime_namespaceObject=require("react/jsx-runtime"),theme_namespaceObject=require("@c15t/ui/theme"),utils_namespaceObject=require("@c15t/ui/utils"),external_c15t_namespaceObject=require("c15t"),external_react_namespaceObject=require("react"),consent_manager_context_cjs_namespaceObject=require("../context/consent-manager-context.cjs"),theme_context_cjs_namespaceObject=require("../context/theme-context.cjs"),use_color_scheme_cjs_namespaceObject=require("../hooks/use-color-scheme.cjs"),theme_utils_cjs_namespaceObject=require("../utils/theme-utils.cjs"),external_version_cjs_namespaceObject=require("../version.cjs");function clearConsentRuntimeCache(){(0,external_c15t_namespaceObject.clearConsentRuntimeCache)()}const CALLBACK_KEYS=["onBannerFetched","onConsentSet","onConsentChanged","onError","onBeforeConsentRevocationReload"];function pickCallbackProps(e){return{onBannerFetched:e?.onBannerFetched,onConsentSet:e?.onConsentSet,onConsentChanged:e?.onConsentChanged,onError:e?.onError,onBeforeConsentRevocationReload:e?.onBeforeConsentRevocationReload}}function ConsentManagerProvider({children:e,options:t}){let{consentManager:r,consentStore:n}=(0,external_react_namespaceObject.useMemo)(()=>(0,external_c15t_namespaceObject.getOrCreateConsentRuntime)(t,{pkg:"@c15t/react",version:external_version_cjs_namespaceObject.version}),[t]),[a,c]=(0,external_react_namespaceObject.useState)(()=>n?n.getState():{}),o=(0,external_react_namespaceObject.useRef)(!1),_=(0,external_react_namespaceObject.useRef)(pickCallbackProps(t.callbacks));(0,external_react_namespaceObject.useEffect)(()=>{if(!n)return;let e=n.subscribe(c);if(!o.current){let e=n.getState();(0,external_react_namespaceObject.startTransition)(()=>{c(t=>t!==e?(o.current=!0,e):(o.current=!0,t))})}return e},[n]),(0,external_react_namespaceObject.useEffect)(()=>{if(!n)return;let e=n.getState().overrides??{},r=t.overrides??{};(e.country!==r.country||e.region!==r.region||e.language!==r.language||e.gpc!==r.gpc)&&n.getState().setOverrides({country:r.country,region:r.region,language:r.language,gpc:r.gpc})},[n,t.overrides?.country,t.overrides?.region,t.overrides?.language,t.overrides?.gpc]),(0,external_react_namespaceObject.useEffect)(()=>{if(!n)return;let e=pickCallbackProps(t.callbacks),r=_.current;CALLBACK_KEYS.some(t=>r[t]!==e[t])&&(n.setState(t=>({callbacks:{...t.callbacks,...e}})),_.current=e)},[n,t.callbacks?.onBannerFetched,t.callbacks?.onConsentSet,t.callbacks?.onConsentChanged,t.callbacks?.onError,t.callbacks?.onBeforeConsentRevocationReload]);let s=(0,external_react_namespaceObject.useMemo)(()=>{let{theme:e={},noStyle:r,disableAnimation:n,trapFocus:a=!0,colorScheme:c}=t;return{theme:(0,utils_namespaceObject.deepMerge)(theme_utils_cjs_namespaceObject.defaultTheme,e),noStyle:r,disableAnimation:n,trapFocus:a,colorScheme:c}},[t]),i=(0,external_react_namespaceObject.useMemo)(()=>(0,theme_namespaceObject.generateThemeCSS)(s.theme),[s.theme]);(0,use_color_scheme_cjs_namespaceObject.useColorScheme)(t.colorScheme);let l=(0,external_react_namespaceObject.useMemo)(()=>{if(!n)throw Error("Consent store must be initialized before creating context value");return{state:a,store:n,manager:r}},[a,n,r]);return(0,jsx_runtime_namespaceObject.jsx)(consent_manager_context_cjs_namespaceObject.ConsentStateContext.Provider,{value:l,children:(0,jsx_runtime_namespaceObject.jsxs)(theme_context_cjs_namespaceObject.GlobalThemeContext.Provider,{value:s,children:[i?(0,jsx_runtime_namespaceObject.jsx)("style",{id:"c15t-theme",dangerouslySetInnerHTML:{__html:i}}):null,e]})})}for(var __rspack_i in exports.ConsentManagerProvider=__webpack_exports__.ConsentManagerProvider,exports.clearConsentRuntimeCache=__webpack_exports__.clearConsentRuntimeCache,__webpack_exports__)-1===["ConsentManagerProvider","clearConsentRuntimeCache"].indexOf(__rspack_i)&&(exports[__rspack_i]=__webpack_exports__[__rspack_i]);Object.defineProperty(exports,"__esModule",{value:!0});
1
+ "use strict";const __rslib_import_meta_url__="u"<typeof document?new(require("url".replace("",""))).URL("file:"+__filename).href:document.currentScript&&document.currentScript.src||new URL("main.js",document.baseURI).href;var __webpack_require__={};__webpack_require__.d=(e,t,r)=>{var n=(t,r)=>{for(var n in t)__webpack_require__.o(t,n)&&!__webpack_require__.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,[r]:t[n]})};n(t,"get"),n(r,"value")},__webpack_require__.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),__webpack_require__.r=e=>{"u">typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var __webpack_exports__={};__webpack_require__.r(__webpack_exports__),__webpack_require__.d(__webpack_exports__,{ConsentManagerProvider:()=>ConsentManagerProvider,clearConsentRuntimeCache:()=>clearConsentRuntimeCache});const jsx_runtime_namespaceObject=require("react/jsx-runtime"),theme_namespaceObject=require("@c15t/ui/theme"),utils_namespaceObject=require("@c15t/ui/utils"),external_c15t_namespaceObject=require("c15t"),external_react_namespaceObject=require("react"),consent_manager_context_cjs_namespaceObject=require("../context/consent-manager-context.cjs"),theme_context_cjs_namespaceObject=require("../context/theme-context.cjs"),use_color_scheme_cjs_namespaceObject=require("../hooks/use-color-scheme.cjs"),theme_utils_cjs_namespaceObject=require("../utils/theme-utils.cjs"),external_version_cjs_namespaceObject=require("../version.cjs");function clearConsentRuntimeCache(){(0,external_c15t_namespaceObject.clearConsentRuntimeCache)()}const CALLBACK_KEYS=["onBannerFetched","onConsentSet","onConsentChanged","onError","onBeforeConsentRevocationReload"];function pickCallbackProps(e){return{onBannerFetched:e?.onBannerFetched,onConsentSet:e?.onConsentSet,onConsentChanged:e?.onConsentChanged,onError:e?.onError,onBeforeConsentRevocationReload:e?.onBeforeConsentRevocationReload}}function ConsentManagerProvider({children:e,options:t}){let{consentManager:r,consentStore:n}=(0,external_react_namespaceObject.useMemo)(()=>(0,external_c15t_namespaceObject.getOrCreateConsentRuntime)(t,{pkg:"@c15t/react",version:external_version_cjs_namespaceObject.version}),[t]),[a,c]=(0,external_react_namespaceObject.useState)(()=>n?n.getState():{}),o=(0,external_react_namespaceObject.useRef)(!1),_=(0,external_react_namespaceObject.useRef)(pickCallbackProps(t.callbacks));(0,external_react_namespaceObject.useEffect)(()=>{if(!n)return;let e=n.subscribe(c);if(!o.current){let e=n.getState();(0,external_react_namespaceObject.startTransition)(()=>{c(t=>t!==e?(o.current=!0,e):(o.current=!0,t))})}return e},[n]),(0,external_react_namespaceObject.useEffect)(()=>{if(!n)return;let e=n.getState().overrides??{},r=t.overrides??{};(e.country!==r.country||e.region!==r.region||e.language!==r.language||e.gpc!==r.gpc)&&n.getState().setOverrides({country:r.country,region:r.region,language:r.language,gpc:r.gpc})},[n,t.overrides?.country,t.overrides?.region,t.overrides?.language,t.overrides?.gpc]),(0,external_react_namespaceObject.useEffect)(()=>{if(!n)return;let e=pickCallbackProps(t.callbacks),r=_.current;CALLBACK_KEYS.some(t=>r[t]!==e[t])&&(n.setState(t=>({callbacks:{...t.callbacks,...e}})),_.current=e)},[n,t.callbacks?.onBannerFetched,t.callbacks?.onConsentSet,t.callbacks?.onConsentChanged,t.callbacks?.onError,t.callbacks?.onBeforeConsentRevocationReload]);let s=(0,external_react_namespaceObject.useMemo)(()=>{let{theme:e={},noStyle:r,disableAnimation:n,trapFocus:a=!0,colorScheme:c}=t;return{theme:(0,utils_namespaceObject.deepMerge)(theme_utils_cjs_namespaceObject.defaultTheme,e),noStyle:r,disableAnimation:n,trapFocus:a,colorScheme:c}},[t]),i=(0,external_react_namespaceObject.useMemo)(()=>(0,theme_namespaceObject.generateThemeCSS)(s.theme),[s.theme]),l=t.nonce??t.store?.nonce;(0,use_color_scheme_cjs_namespaceObject.useColorScheme)(t.colorScheme);let u=(0,external_react_namespaceObject.useMemo)(()=>{if(!n)throw Error("Consent store must be initialized before creating context value");return{state:a,store:n,manager:r}},[a,n,r]);return(0,jsx_runtime_namespaceObject.jsx)(consent_manager_context_cjs_namespaceObject.ConsentStateContext.Provider,{value:u,children:(0,jsx_runtime_namespaceObject.jsxs)(theme_context_cjs_namespaceObject.GlobalThemeContext.Provider,{value:s,children:[i?(0,jsx_runtime_namespaceObject.jsx)("style",{id:"c15t-theme",nonce:l,dangerouslySetInnerHTML:{__html:i}}):null,e]})})}for(var __rspack_i in exports.ConsentManagerProvider=__webpack_exports__.ConsentManagerProvider,exports.clearConsentRuntimeCache=__webpack_exports__.clearConsentRuntimeCache,__webpack_exports__)-1===["ConsentManagerProvider","clearConsentRuntimeCache"].indexOf(__rspack_i)&&(exports[__rspack_i]=__webpack_exports__[__rspack_i]);Object.defineProperty(exports,"__esModule",{value:!0});
@@ -1 +1 @@
1
- import{jsx as e,jsxs as n}from"react/jsx-runtime";import{generateThemeCSS as t}from"@c15t/ui/theme";import{deepMerge as r}from"@c15t/ui/utils";import{clearConsentRuntimeCache as o,getOrCreateConsentRuntime as a}from"c15t";import{startTransition as c,useEffect as s,useMemo as i,useRef as l,useState as u}from"react";import{ConsentStateContext as m}from"../context/consent-manager-context.js";import{GlobalThemeContext as g}from"../context/theme-context.js";import{useColorScheme as d}from"../hooks/use-color-scheme.js";import{defaultTheme as h}from"../utils/theme-utils.js";import{version as f}from"../version.js";function C(){o()}let v=["onBannerFetched","onConsentSet","onConsentChanged","onError","onBeforeConsentRevocationReload"];function p(e){return{onBannerFetched:e?.onBannerFetched,onConsentSet:e?.onConsentSet,onConsentChanged:e?.onConsentChanged,onError:e?.onError,onBeforeConsentRevocationReload:e?.onBeforeConsentRevocationReload}}function b({children:o,options:C}){let{consentManager:k,consentStore:S}=i(()=>a(C,{pkg:"@c15t/react",version:f}),[C]),[R,B]=u(()=>S?S.getState():{}),x=l(!1),y=l(p(C.callbacks));s(()=>{if(!S)return;let e=S.subscribe(B);if(!x.current){let e=S.getState();c(()=>{B(n=>n!==e?(x.current=!0,e):(x.current=!0,n))})}return e},[S]),s(()=>{if(!S)return;let e=S.getState().overrides??{},n=C.overrides??{};(e.country!==n.country||e.region!==n.region||e.language!==n.language||e.gpc!==n.gpc)&&S.getState().setOverrides({country:n.country,region:n.region,language:n.language,gpc:n.gpc})},[S,C.overrides?.country,C.overrides?.region,C.overrides?.language,C.overrides?.gpc]),s(()=>{if(!S)return;let e=p(C.callbacks),n=y.current;v.some(t=>n[t]!==e[t])&&(S.setState(n=>({callbacks:{...n.callbacks,...e}})),y.current=e)},[S,C.callbacks?.onBannerFetched,C.callbacks?.onConsentSet,C.callbacks?.onConsentChanged,C.callbacks?.onError,C.callbacks?.onBeforeConsentRevocationReload]);let j=i(()=>{let{theme:e={},noStyle:n,disableAnimation:t,trapFocus:o=!0,colorScheme:a}=C;return{theme:r(h,e),noStyle:n,disableAnimation:t,trapFocus:o,colorScheme:a}},[C]),E=i(()=>t(j.theme),[j.theme]);d(C.colorScheme);let _=i(()=>{if(!S)throw Error("Consent store must be initialized before creating context value");return{state:R,store:S,manager:k}},[R,S,k]);return e(m.Provider,{value:_,children:n(g.Provider,{value:j,children:[E?e("style",{id:"c15t-theme",dangerouslySetInnerHTML:{__html:E}}):null,o]})})}export{b as ConsentManagerProvider,C as clearConsentRuntimeCache};
1
+ import{jsx as e,jsxs as n}from"react/jsx-runtime";import{generateThemeCSS as t}from"@c15t/ui/theme";import{deepMerge as r}from"@c15t/ui/utils";import{clearConsentRuntimeCache as o,getOrCreateConsentRuntime as c}from"c15t";import{startTransition as a,useEffect as s,useMemo as i,useRef as l,useState as u}from"react";import{ConsentStateContext as m}from"../context/consent-manager-context.js";import{GlobalThemeContext as g}from"../context/theme-context.js";import{useColorScheme as d}from"../hooks/use-color-scheme.js";import{defaultTheme as h}from"../utils/theme-utils.js";import{version as f}from"../version.js";function C(){o()}let v=["onBannerFetched","onConsentSet","onConsentChanged","onError","onBeforeConsentRevocationReload"];function p(e){return{onBannerFetched:e?.onBannerFetched,onConsentSet:e?.onConsentSet,onConsentChanged:e?.onConsentChanged,onError:e?.onError,onBeforeConsentRevocationReload:e?.onBeforeConsentRevocationReload}}function b({children:o,options:C}){let{consentManager:k,consentStore:S}=i(()=>c(C,{pkg:"@c15t/react",version:f}),[C]),[R,B]=u(()=>S?S.getState():{}),x=l(!1),y=l(p(C.callbacks));s(()=>{if(!S)return;let e=S.subscribe(B);if(!x.current){let e=S.getState();a(()=>{B(n=>n!==e?(x.current=!0,e):(x.current=!0,n))})}return e},[S]),s(()=>{if(!S)return;let e=S.getState().overrides??{},n=C.overrides??{};(e.country!==n.country||e.region!==n.region||e.language!==n.language||e.gpc!==n.gpc)&&S.getState().setOverrides({country:n.country,region:n.region,language:n.language,gpc:n.gpc})},[S,C.overrides?.country,C.overrides?.region,C.overrides?.language,C.overrides?.gpc]),s(()=>{if(!S)return;let e=p(C.callbacks),n=y.current;v.some(t=>n[t]!==e[t])&&(S.setState(n=>({callbacks:{...n.callbacks,...e}})),y.current=e)},[S,C.callbacks?.onBannerFetched,C.callbacks?.onConsentSet,C.callbacks?.onConsentChanged,C.callbacks?.onError,C.callbacks?.onBeforeConsentRevocationReload]);let j=i(()=>{let{theme:e={},noStyle:n,disableAnimation:t,trapFocus:o=!0,colorScheme:c}=C;return{theme:r(h,e),noStyle:n,disableAnimation:t,trapFocus:o,colorScheme:c}},[C]),E=i(()=>t(j.theme),[j.theme]),_=C.nonce??C.store?.nonce;d(C.colorScheme);let F=i(()=>{if(!S)throw Error("Consent store must be initialized before creating context value");return{state:R,store:S,manager:k}},[R,S,k]);return e(m.Provider,{value:F,children:n(g.Provider,{value:j,children:[E?e("style",{id:"c15t-theme",nonce:_,dangerouslySetInnerHTML:{__html:E}}):null,o]})})}export{b as ConsentManagerProvider,C as clearConsentRuntimeCache};
package/dist/version.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";const __rslib_import_meta_url__="u"<typeof document?new(require("url".replace("",""))).URL("file:"+__filename).href:document.currentScript&&document.currentScript.src||new URL("main.js",document.baseURI).href;var __webpack_require__={};__webpack_require__.d=(e,_,r)=>{var o=(_,r)=>{for(var o in _)__webpack_require__.o(_,o)&&!__webpack_require__.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,[r]:_[o]})};o(_,"get"),o(r,"value")},__webpack_require__.o=(e,_)=>Object.prototype.hasOwnProperty.call(e,_),__webpack_require__.r=e=>{"u">typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var __webpack_exports__={};__webpack_require__.r(__webpack_exports__);const version="2.2.0-canary-20260727202135";for(var __rspack_i in __webpack_require__.d(__webpack_exports__,{},{version:"2.2.0-canary-20260727202135"}),exports.version=__webpack_exports__.version,__webpack_exports__)-1===["version"].indexOf(__rspack_i)&&(exports[__rspack_i]=__webpack_exports__[__rspack_i]);Object.defineProperty(exports,"__esModule",{value:!0});
1
+ "use strict";const __rslib_import_meta_url__="u"<typeof document?new(require("url".replace("",""))).URL("file:"+__filename).href:document.currentScript&&document.currentScript.src||new URL("main.js",document.baseURI).href;var __webpack_require__={};__webpack_require__.d=(e,_,r)=>{var o=(_,r)=>{for(var o in _)__webpack_require__.o(_,o)&&!__webpack_require__.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,[r]:_[o]})};o(_,"get"),o(r,"value")},__webpack_require__.o=(e,_)=>Object.prototype.hasOwnProperty.call(e,_),__webpack_require__.r=e=>{"u">typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var __webpack_exports__={};__webpack_require__.r(__webpack_exports__);const version="2.2.0";for(var __rspack_i in __webpack_require__.d(__webpack_exports__,{},{version:"2.2.0"}),exports.version=__webpack_exports__.version,__webpack_exports__)-1===["version"].indexOf(__rspack_i)&&(exports[__rspack_i]=__webpack_exports__[__rspack_i]);Object.defineProperty(exports,"__esModule",{value:!0});
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- let e="2.2.0-canary-20260727202135";export{e as version};
1
+ let e="2.2.0";export{e as version};
@@ -1 +1 @@
1
- export declare const version = "2.2.0-canary-20260727202135";
1
+ export declare const version = "2.2.0";
@@ -75,6 +75,48 @@ export default function ConsentManager({ children }: { children: ReactNode }) {
75
75
 
76
76
  See [Client Modes](/docs/frameworks/react/concepts/client-modes) for a detailed comparison.
77
77
 
78
+ ## Content Security Policy
79
+
80
+ c15t injects a `<style id="c15t-theme">` element for your theme tokens, and the script loader injects a `<script>` element per consented vendor. Under a nonce-based Content Security Policy, both are blocked unless they carry your nonce.
81
+
82
+ Pass it once through the `nonce` option and c15t applies it to everything it injects:
83
+
84
+ ```tsx
85
+ <ConsentManagerProvider
86
+ options={{
87
+ mode: 'offline',
88
+ nonce: yourRequestNonce,
89
+ }}
90
+ >
91
+ {children}
92
+ </ConsentManagerProvider>
93
+ ```
94
+
95
+ A `nonce` set on an individual script definition still wins, so you can override a single vendor without changing the provider.
96
+
97
+ > 📝 **Note:**
98
+ > Browsers hide the nonce content attribute once a policy is active. Inspecting the element shows no nonce="", but element.nonce still returns the value — this is expected and not a sign that c15t dropped it.
99
+
100
+ ### Inline style attributes
101
+
102
+ The `nonce` option covers the elements c15t injects. It cannot cover inline `style="..."` attributes, which several components rely on — a nonce never authorizes a style attribute, because nonces apply to elements only.
103
+
104
+ Style attributes are governed by `style-src-attr`, and when that directive is absent CSP falls back to `style-src`. A nonce-based style policy therefore blocks them:
105
+
106
+ ```http
107
+ style-src 'self' 'nonce-abc123';
108
+ ```
109
+
110
+ To keep the nonce requirement for stylesheets while still allowing style attributes, set `style-src-attr` explicitly:
111
+
112
+ ```http
113
+ style-src 'self' 'nonce-abc123';
114
+ style-src-attr 'unsafe-inline';
115
+ ```
116
+
117
+ > ⚠️ **Warning:**
118
+ > 'unsafe-inline' on style-src-attr permits any inline style attribute on the page. That is weaker than a nonce, but far narrower than replacing your nonce-based style-src with 'unsafe-inline', which would additionally authorize arbitrary injected \<style> elements. Note that adding 'unsafe-inline' alongside a nonce achieves nothing — a directive that specifies a nonce ignores it. If your threat model does not allow this, expect components that use inline styles to render unstyled.
119
+
78
120
  ## Legal Links
79
121
 
80
122
  `legalLinks` defines the URLs shown in consent UI text (banner, dialog, and widget where applicable).
@@ -296,6 +296,8 @@ Control where the script is injected and whether the element id is anonymized:
296
296
 
297
297
  Set `anonymizeId: false` only when another script or test needs a stable DOM id. Pass `nonce` when your CSP requires it; c15t applies it directly to the generated `<script>` element.
298
298
 
299
+ You usually do not need a per-script `nonce`. Setting `nonce` once on the provider covers every injected script (and the theme stylesheet); a per-script value overrides it for that script alone.
300
+
299
301
  ## Dynamic Management
300
302
 
301
303
  Framework packages expose script-manager methods so integrations can be added, removed, or inspected at runtime. Use this for tenant-specific tools, feature-flagged scripts, or vendors that are configured after sign-in:
@@ -83,7 +83,7 @@ This is the recommended approach if you're using the PostHog JS SDK; it's common
83
83
 
84
84
  If you want to load PostHog via a script tag, it's recommended to use this approach.
85
85
 
86
- 1. **Choose a region and loading mode** The c15t helper loads PostHog's bootstrap script, calls `posthog.init()` for you, and then synchronizes consent through `posthog.opt_in_capturing()` / `posthog.opt_out_capturing()`. You do not need to call `posthog.init()` separately.
86
+ 1. **Choose a region and loading mode** The c15t helper seeds PostHog's initialization queue, loads the bootstrap script, and synchronizes consent through `posthog.opt_in_capturing()` / `posthog.opt_out_capturing()`. You do not need to call `posthog.init()` separately.
87
87
 
88
88
  Use region to keep PostHog's API, UI, and bootstrap script hosts aligned. c15t defaults to region: 'eu'; set region: 'us' for PostHog Cloud US. You can still pass apiHost, uiHost, or scriptUrl for self-hosted or proxied setups.
89
89
 
@@ -221,7 +221,7 @@ posthog({
221
221
  The behavior depends on which pattern you chose:
222
222
 
223
223
  * **SDK Implementation** — your app loaded `posthog-js` itself, so `posthog.capture(...)` is available once your SDK setup has run. c15t calls `opt_in_capturing()` / `opt_out_capturing()` for you. Pending events before c15t syncs consent may be dropped; after denial, PostHog captures cookieless events.
224
- * **Script Implementation with `loadMode: 'always'`** — `window.posthog` is defined early. c15t calls `opt_in_capturing()` / `opt_out_capturing()` based on consent. Pending events before the PostHog bootstrap finishes may be dropped; after denial, PostHog captures cookieless events when your PostHog project supports cookieless mode.
224
+ * **Script Implementation with `loadMode: 'always'`** — `window.posthog` is defined early as a queue. Calls to `posthog.capture(...)` before the bootstrap finishes are replayed after the SDK installs instead of being dropped. c15t queues the current consent decision ahead of those calls, then keeps calling `opt_in_capturing()` / `opt_out_capturing()` as consent changes. After denial, PostHog captures cookieless events when your PostHog project supports cookieless mode.
225
225
  * **Script Implementation with `loadMode: 'after-consent'`** — PostHog is unavailable until measurement consent is granted. Guard `posthog.capture(...)` calls or call them only after consent.
226
226
 
227
227
  > ⚠️ **Warning:**
@@ -30,6 +30,48 @@ Every other c15t component and hook must be rendered inside this provider.
30
30
 
31
31
  \*ExtractedTypeTable: Could not extract "UIOptions" from "./packages/ui/src/theme/types.ts" using base path "/home/runner/work/c15t/c15t". Verify the path/name and that the file is included by your tsconfig.\*
32
32
 
33
+ ## Content Security Policy
34
+
35
+ c15t injects a `<style id="c15t-theme">` element for your theme tokens, and the script loader injects a `<script>` element per consented vendor. Under a nonce-based Content Security Policy, both are blocked unless they carry your nonce.
36
+
37
+ Pass it once through the `nonce` option and c15t applies it to everything it injects:
38
+
39
+ ```tsx
40
+ <ConsentManagerProvider
41
+ options={{
42
+ mode: 'offline',
43
+ nonce: yourRequestNonce,
44
+ }}
45
+ >
46
+ {children}
47
+ </ConsentManagerProvider>
48
+ ```
49
+
50
+ A `nonce` set on an individual script definition still wins, so you can override a single vendor without changing the provider.
51
+
52
+ > 📝 **Note:**
53
+ > Browsers hide the nonce content attribute once a policy is active. Inspecting the element shows no nonce="", but element.nonce still returns the value — this is expected and not a sign that c15t dropped it.
54
+
55
+ ### Inline style attributes
56
+
57
+ The `nonce` option covers the elements c15t injects. It cannot cover inline `style="..."` attributes, which several components rely on — a nonce never authorizes a style attribute, because nonces apply to elements only.
58
+
59
+ Style attributes are governed by `style-src-attr`, and when that directive is absent CSP falls back to `style-src`. A nonce-based style policy therefore blocks them:
60
+
61
+ ```http
62
+ style-src 'self' 'nonce-abc123';
63
+ ```
64
+
65
+ To keep the nonce requirement for stylesheets while still allowing style attributes, set `style-src-attr` explicitly:
66
+
67
+ ```http
68
+ style-src 'self' 'nonce-abc123';
69
+ style-src-attr 'unsafe-inline';
70
+ ```
71
+
72
+ > ⚠️ **Warning:**
73
+ > 'unsafe-inline' on style-src-attr permits any inline style attribute on the page. That is weaker than a nonce, but far narrower than replacing your nonce-based style-src with 'unsafe-inline', which would additionally authorize arbitrary injected \<style> elements. Note that adding 'unsafe-inline' alongside a nonce achieves nothing — a directive that specifies a nonce ignores it. If your threat model does not allow this, expect components that use inline styles to render unstyled.
74
+
33
75
  ## Legal Links
34
76
 
35
77
  `legalLinks` defines the URLs shown in consent UI text (banner, dialog, and widget where applicable).
@@ -229,6 +229,8 @@ Control where the script is injected and whether the element id is anonymized:
229
229
 
230
230
  Set `anonymizeId: false` only when another script or test needs a stable DOM id. Pass `nonce` when your CSP requires it; c15t applies it directly to the generated `<script>` element.
231
231
 
232
+ You usually do not need a per-script `nonce`. Setting `nonce` once on the provider covers every injected script (and the theme stylesheet); a per-script value overrides it for that script alone.
233
+
232
234
  ## Dynamic Management
233
235
 
234
236
  Framework packages expose script-manager methods so integrations can be added, removed, or inspected at runtime. Use this for tenant-specific tools, feature-flagged scripts, or vendors that are configured after sign-in:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c15t/react",
3
- "version": "2.2.0-canary-20260727202135",
3
+ "version": "2.2.0",
4
4
  "description": "React cookie banner, consent manager, preference center, and headless CMP components with IAB TCF and Consent Mode support.",
5
5
  "keywords": [
6
6
  "react",
@@ -163,13 +163,13 @@
163
163
  "not op_mini all"
164
164
  ],
165
165
  "dependencies": {
166
- "@c15t/ui": "2.2.0-canary-20260727202135",
166
+ "@c15t/ui": "2.2.0",
167
167
  "@types/google.maps": "3.65.3",
168
- "c15t": "2.2.0-canary-20260727202135"
168
+ "c15t": "2.2.0"
169
169
  },
170
170
  "devDependencies": {
171
- "@c15t/backend": "2.2.0-canary-20260727202135",
172
- "@c15t/iab": "2.2.0-canary-20260727202135",
171
+ "@c15t/backend": "2.2.0",
172
+ "@c15t/iab": "2.2.0",
173
173
  "@c15t/typescript-config": "0.0.1",
174
174
  "@c15t/vitest-config": "1.0.0",
175
175
  "@iabtechlabtcf/core": "^1.5.20",