@c15t/react 3.1.0-canary.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/event-dispatcher.js +2 -0
- package/dist/index-parts/event-dispatcher.js +1 -0
- package/dist/index.js +1 -1
- package/dist/provider.js +1 -1
- package/dist/version.js +1 -1
- package/dist-types/context.d.ts +3 -1
- package/dist-types/event-dispatcher.d.ts +24 -0
- package/dist-types/index-parts/event-dispatcher.d.ts +7 -0
- package/dist-types/index.d.ts +2 -0
- package/dist-types/version.d.ts +1 -1
- package/docs/concepts/consent-state.md +12 -0
- package/docs/frameworks/react/scripts.md +44 -0
- package/docs/integrations/overview.md +48 -25
- package/docs/integrations/posthog.md +8 -0
- package/package.json +14 -9
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
</a>
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
|
-
# @c15t/react: React
|
|
10
|
+
# @c15t/react: React cookie banner and cookie consent components
|
|
11
11
|
|
|
12
12
|
<p>
|
|
13
13
|
<a href="https://www.npmjs.com/package/@c15t/react"><picture><source media="(prefers-color-scheme: dark)" srcset="https://shieldcn.dev/npm/%40c15t%2Freact.svg?variant=outline&mode=dark"><img src="https://shieldcn.dev/npm/%40c15t%2Freact.svg?variant=outline&mode=light" alt="Latest NPM Version"></picture></a>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
<a href="https://inth.com?utm_source=npm&utm_medium=readme&utm_campaign=oss_readme&utm_content=%40c15t%2Freact"><picture><source media="(prefers-color-scheme: dark)" srcset="https://shieldcn.dev/badge/Made%20By-Inth-ffc803.svg?color=ffc803&labelTextColor=000000&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAzOTMgNDAwIj48cGF0aCBmaWxsPSIjMDAwIiBkPSJNMTgyLjY2MiAwdjM2Ljg5NWgtNTkuMDMxdjgyLjczM2g1OS4wMzF2MzYuODkzSDI3LjQ4MnYtMzYuODkzaDU5LjAzVjM2Ljg5NWgtNTkuMDNWMHpNMzIxLjk0MSA4OS44NVYwaDM1LjM1NXYxNTYuNTIxaC0yNS43MTNsLTg2LjEzNy05MC4zNjR2OTAuMzY0aC0zNS4zNTVWMGgyNi4zNTV6Ii8%2BPHBhdGggZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzE4LjU3MSAxODUuNzE0aDc0LjI4NlY0MDBIMFYxODUuNzE0aDI3Mi44NTd2LTQ3LjE0M3ptLTI5MS4wOSAyOC45Njl2MzcuMTE4aDU4LjEzN3YxMTkuNjI4aDM2Ljg5NVYyNTEuODAxaDU4LjU4NHYtMzcuMTE4em0xODIuNjEuMjI0djE1Ni41MjJoMzYuODk0VjMxMy41OWg3My4zNDF2NTcuODM5aDM3LjExOFYyMTQuOTA3aC0zNy4xMTh2NjEuNzg4aC03My4zNDF2LTYxLjc4OHoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg%3D%3D&valueColor=000000&mode=dark"><img src="https://shieldcn.dev/badge/Made%20By-Inth-ffc803.svg?color=ffc803&labelTextColor=000000&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAzOTMgNDAwIj48cGF0aCBmaWxsPSIjMDAwIiBkPSJNMTgyLjY2MiAwdjM2Ljg5NWgtNTkuMDMxdjgyLjczM2g1OS4wMzF2MzYuODkzSDI3LjQ4MnYtMzYuODkzaDU5LjAzVjM2Ljg5NWgtNTkuMDNWMHpNMzIxLjk0MSA4OS44NVYwaDM1LjM1NXYxNTYuNTIxaC0yNS43MTNsLTg2LjEzNy05MC4zNjR2OTAuMzY0aC0zNS4zNTVWMGgyNi4zNTV6Ii8%2BPHBhdGggZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzE4LjU3MSAxODUuNzE0aDc0LjI4NlY0MDBIMFYxODUuNzE0aDI3Mi44NTd2LTQ3LjE0M3ptLTI5MS4wOSAyOC45Njl2MzcuMTE4aDU4LjEzN3YxMTkuNjI4aDM2Ljg5NVYyNTEuODAxaDU4LjU4NHYtMzcuMTE4em0xODIuNjEuMjI0djE1Ni41MjJoMzYuODk0VjMxMy41OWg3My4zNDF2NTcuODM5aDM3LjExOFYyMTQuOTA3aC0zNy4xMTh2NjEuNzg4aC03My4zNDF2LTYxLjc4OHoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg%3D%3D&valueColor=000000&mode=light" alt="Made by Inth"></picture></a>
|
|
19
19
|
</p>
|
|
20
20
|
|
|
21
|
-
React cookie banner, consent dialog, and preference center for React 18 and 19,
|
|
21
|
+
React cookie banner, consent dialog, and preference center for React 18 and 19. Each visitor's consent policy decides whether optional cookies wait for an opt-in, as EU cookie rules require, or load until the visitor opts out. Use headless hooks when you want your own design.
|
|
22
22
|
|
|
23
23
|
c15t is used by [2,100+ sites](https://www.wappalyzer.com/technologies/cookie-compliance/c15t/), including expo.dev, zed.dev, unkey.dev, mintlify.com, and sazabi.com.
|
|
24
24
|
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import{getEventDispatcher as e}from"@c15t/core/events";import{useContext as t,useMemo as r}from"react";import{ProviderServicesContext as o}from"./context.js";import{useCommittedRef as c}from"./hooks/use-committed-ref.js";let s=function(s={}){let i=t(o);if(!i)throw Error("c15t: useEventDispatcher() needs <ConsentProvider> from @c15t/react.");let{runtime:n}=i,a=c(s);return r(()=>({pageview:t=>e(n,a.current).pageview(t),track:(t,r)=>e(n,a.current).track(t,r)}),[a,n])};export{s as useEventDispatcher};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{useEventDispatcher}from"../event-dispatcher.js";
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export*from"./index-parts/core.js";export*from"./index-parts/hosted.js";export*from"./index-parts/offline.js";export*from"./index-parts/presets.js";export*from"./index-parts/consent-dialog.js";export*from"./index-parts/consent-banner.js";export*from"./index-parts/consent-dialog-link.js";export*from"./index-parts/consent-dialog-trigger.js";export*from"./index-parts/consent-gate.js";export*from"./index-parts/frame.js";export*from"./index-parts/draft.js";export*from"./index-parts/translations.js";export*from"./index-parts/module-hooks.js";export*from"./index-parts/provider.js";export*from"./index-parts/theme.js";export*from"./index-parts/hooks.js";
|
|
1
|
+
export*from"./index-parts/core.js";export*from"./index-parts/hosted.js";export*from"./index-parts/offline.js";export*from"./index-parts/presets.js";export*from"./index-parts/consent-dialog.js";export*from"./index-parts/consent-banner.js";export*from"./index-parts/consent-dialog-link.js";export*from"./index-parts/consent-dialog-trigger.js";export*from"./index-parts/consent-gate.js";export*from"./index-parts/frame.js";export*from"./index-parts/draft.js";export*from"./index-parts/event-dispatcher.js";export*from"./index-parts/translations.js";export*from"./index-parts/module-hooks.js";export*from"./index-parts/provider.js";export*from"./index-parts/theme.js";export*from"./index-parts/hooks.js";
|
package/dist/provider.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
let e,t;import{jsx as o,jsxs as r}from"react/jsx-runtime";import{applyExperimentAssignment as n,applyExperimentTheme as i,hostExperiment as s,watchRevocationReload as c}from"@c15t/core";import{createPersistence as l,readStoredRecords as a}from"@c15t/core/modules/persistence";import{createWindowDebug as d}from"@c15t/core/modules/window-debug";import{claimEarlyJourney as u,createConsentProviderRuntime as m,earlyInitModes as p,lazyRuntimeModule as h,lazyStreamPrefetch as f,readHostedMode as g,streamPrefetchWith as v}from"@c15t/core/runtime/provider";import{applyThemeSlots as y}from"@c15t/ui/utils";import{useCallback as k,useEffect as S,useLayoutEffect as b,useMemo as w,useReducer as C,useState as x,useSyncExternalStore as j}from"react";import{KernelContext as L,ProviderServicesContext as N,StreamedPrefetchContext as O}from"./context.js";import{ExternalIABProvider as P}from"./external-iab-context.js";import{useColorScheme as D}from"./hooks/use-color-scheme.js";import{useIsHydrated as R}from"./hooks/use-is-hydrated.js";import{createIframeBlockerOnDemand as _}from"./module-hooks/iframe-blocker.js";import{UNCOMMITTED_HOLD_MS as T}from"./module-hooks/network-hold.js";import{V3ThemeProvider as E}from"./theme-provider.js";let q=(e,t)=>{let o,r=!1;return(async()=>{try{let{connectConsentSource:n}=await import("@c15t/core/runtime/controls");r||(o=n(e,t))}catch{}})(),()=>{r=!0,o?.()}},B=async()=>(await import("@c15t/core/modules/script-loader")).createScriptLoader,A=function(e){let t=e.__resolveStreamedInit;return{connectConsentSource:q,createClearOnRevocation:h(async()=>(await import("@c15t/core/modules/clear-on-revocation")).createClearOnRevocation),createIframeBlocker:_,createNetworkBlocker:h(async()=>(await import("@c15t/core/modules/network-blocker")).createNetworkBlocker),createPersistence:l,createScriptLoader:h(B),createWindowDebug:d,preloadScriptLoader:e.__preloadScriptLoader?.(B),streamPrefetch:t?v(t):f,watchRevocationReload:c}},I=new WeakMap,J=new Set,K=(e,t)=>{if("hosted"in e){let o,r;return"hosted"in t&&(o=e.hosted,r=t.hosted,o.fetch===r.fetch&&o.initialData===r.initialData&&JSON.stringify(o)===JSON.stringify(r))}return"mode"in t&&e.fetch===t.fetch&&(e.mode===t.mode||e.mode.sameAs(t.mode))},F=function(o){if("function"!=typeof o)return o;let r=I.get(o);return r||(r=Object.assign(r=>e=(t=()=>o(Object.create(r,{iabEnabled:{value:void 0}})))(),{kind:o.kind}),I.set(o,r)),r},M=function(e){return{...e,mode:F(e.mode),pkg:e.__debugPkg??"@c15t/react"}},V=function(e){let{persistence:t}=e;if(!1===t||e.consentSource)return!1;let o="object"==typeof t?t:{};if(o.skipHydration)return!1;let r=o.now?o.now():Date.now(),{records:n}=a(o.storageConfig??e.storageConfig,r);return!!(n.choice||n.noticeDismissal)},W=function(e){let{journey:t}=e;return JSON.stringify({journey:t?[t.id,t.scope,t.storedChoice]:null,overrides:e.overrides,user:e.user})},z=function(e,t,o){if(!(o&&t.policyPending)||e.prefetch||e.experiment)return;let r=g(e.mode);if(r)return{source:{hosted:{...r,fetch:r.fetch??globalThis.fetch}},storedChoice:V(e)};let n=p.get(e.mode);if(!(!n||V(e))&&n.requestsInit(t.overrides))return{source:{fetch:globalThis.fetch,mode:n},storedChoice:!1}},H=new Set,G=0,Q=function(o,r){let n,i,s,{networkBlocker:c}=o,l=m(M(o),A(o)),a=o,d=!1,p=!1,h=0,f=e;G+=1;let g={expire(){J.delete(s),H.delete(g),clearTimeout(n),d=!0,l.dispose()},holdKey:!1!==o.enabled&&c&&!1!==c.enabled?JSON.stringify(c.rules):null,sequence:G};if("u"<typeof window)p=!0;else{H.add(g),n=setTimeout(g.expire,T);let e=l.kernel.getSnapshot(),c=z(o,e,r);if(c){let{source:r,storedChoice:n}=c,l=u({option:o.journey,storedChoice:n}),a=l?{journey:l,overrides:e.overrides,user:e.user}:{overrides:e.overrides,user:e.user},d=W(a);for(let e of J)e.key===d&&K(e.source,r)&&(i=e);if(!i){let e=t(),o=e.init,n=o(a);n.catch(()=>void 0);let c=!1;e.init=e=>{let t=!c&&W(e)===d;return c=!0,t?n:o(e)},i=s={key:d,source:r,transport:e},J.add(s)}}}let v=function(){if(d)return!1;if(p)return!0;if(p=!0,J.delete(i)&&Object.assign(f,i?.transport),H.delete(g),clearTimeout(n),null!==g.holdKey)for(let e of H)e.sequence<g.sequence&&e.holdKey===g.holdKey&&e.expire();return!0};return{apply(e){e!==a&&(a=e,l.update(M(e)).catch(()=>void 0))},mount(){if(!v())return null;let e=h+=1;return l.start(),()=>{queueMicrotask(()=>{h===e&&l.dispose()})}},runtime:l}},U=function(){let e=null;return{get:(t,o)=>e??=Q(t,o),reset(){e=null}}},X=e=>e+1,Y=()=>()=>void 0,Z=function(e){let[t]=x(U),[,o]=C(X,0),r=R(),n=k(()=>{t.reset(),o()},[t]);return e?{entry:t.get(e,r),rebuild:n}:void 0},$="u"<typeof document?S:b,ee=({entry:e,options:t,rebuild:o})=>($(()=>e.apply(t),[e,t]),S(()=>{let t=e.mount();return t||void o()},[e,o]),null),et=e=>{let{children:t}=e,c=e.options??{},[l]=x(()=>e.runtime),a=Z(l?void 0:c),d=a?.entry.runtime,u=l?e.runtime??l:void 0,m=d??u,p=j(d?d.subscribe:Y,()=>m.kernel,()=>m.kernel),[h]=x(()=>{var e;return l?l.experiment??s(c.experiment,(e=c.prefetch,"function"==typeof e?.then)?void 0:c.prefetch):void 0}),f=d?d.experiment:h,g=u?c.consentCategories:void 0;S(()=>{u&&void 0!==g&&u.setConsentCategories(g)},[u,g]);let{presentation:v}=c,k=w(()=>({clearRecords:()=>m.clearRecords(),getConsentCategories:()=>m.consentCategories,getPresentation:()=>n(v,f,p.getSnapshot().experiment),isStarted:()=>m.started,setLanguage:e=>m.setLanguage(e)}),[f,p,v,m]),b=!!u&&void 0!==c.callbacks;S(()=>{"production"!==process.env.NODE_ENV&&b&&console.warn("c15t ConsentProvider: `options.callbacks` is ignored when you pass `runtime`. Pass them to createConsentRuntime({ callbacks }) instead.")},[b]);let C=j(e=>p.subscribe(e),()=>p.getSnapshot().experiment,()=>p.getServerSnapshot().experiment),R=w(()=>i(c.theme,f,C),[c.theme,f,C]);S(()=>{"production"===process.env.NODE_ENV||!R||!["colors","dark","motion","radius","shadows","spacing","typography"].some(e=>e in R)||document.getElementById("c15t-theme")||console.warn("c15t: `theme` tokens are no longer turned into CSS in the browser. Render <ConsentTheme theme={theme} /> on the server, or put the CSS from generateThemeCSS() in your stylesheet. See https://c15t.com/docs/frameworks/react/customize")},[R]);let _=w(()=>({colorScheme:c.colorScheme,disableAnimation:c.disableAnimation,noStyle:c.noStyle,scrollLock:c.scrollLock,theme:R,trapFocus:c.trapFocus}),[R,c.noStyle,c.disableAnimation,c.scrollLock,c.trapFocus,c.colorScheme]),T=w(()=>({components:y(R?.slots,c.components,"className"),experiment:f,legalLinks:c.legalLinks,nonce:c.nonce,preloadDialog:c.preloadDialog,presentation:c.presentation,styles:c.styles,theme:c.theme}),[R?.slots,c.components,f,c.legalLinks,c.nonce,c.preloadDialog,c.presentation,c.styles,c.theme]);return D(c.colorScheme),o(L.Provider,{value:p,children:o(N.Provider,{value:k,children:r(E,{themeConfig:_,uiConfig:T,children:[a?o(ee,{entry:a.entry,options:c,rebuild:a.rebuild}):null,u?o(P,{runtime:u,children:t}):o(O.Provider,{value:!1===c.streamBanner?void 0:d?.streamed,children:t})]})})})};export{et as ConsentProvider};
|
|
2
|
+
let e,t;import{jsx as o,jsxs as r}from"react/jsx-runtime";import{applyExperimentAssignment as n,applyExperimentTheme as i,hostExperiment as s,watchRevocationReload as c}from"@c15t/core";import{createPersistence as l,readStoredRecords as a}from"@c15t/core/modules/persistence";import{createWindowDebug as d}from"@c15t/core/modules/window-debug";import{claimEarlyJourney as u,createConsentProviderRuntime as m,earlyInitModes as p,lazyRuntimeModule as h,lazyStreamPrefetch as f,readHostedMode as g,streamPrefetchWith as v}from"@c15t/core/runtime/provider";import{applyThemeSlots as y}from"@c15t/ui/utils";import{useCallback as k,useEffect as S,useLayoutEffect as b,useMemo as w,useReducer as C,useState as x,useSyncExternalStore as j}from"react";import{KernelContext as L,ProviderServicesContext as N,StreamedPrefetchContext as O}from"./context.js";import{ExternalIABProvider as P}from"./external-iab-context.js";import{useColorScheme as D}from"./hooks/use-color-scheme.js";import{useIsHydrated as R}from"./hooks/use-is-hydrated.js";import{createIframeBlockerOnDemand as _}from"./module-hooks/iframe-blocker.js";import{UNCOMMITTED_HOLD_MS as T}from"./module-hooks/network-hold.js";import{V3ThemeProvider as E}from"./theme-provider.js";let q=(e,t)=>{let o,r=!1;return(async()=>{try{let{connectConsentSource:n}=await import("@c15t/core/runtime/controls");r||(o=n(e,t))}catch{}})(),()=>{r=!0,o?.()}},B=async()=>(await import("@c15t/core/modules/script-loader")).createScriptLoader,A=function(e){let t=e.__resolveStreamedInit;return{connectConsentSource:q,createClearOnRevocation:h(async()=>(await import("@c15t/core/modules/clear-on-revocation")).createClearOnRevocation),createIframeBlocker:_,createNetworkBlocker:h(async()=>(await import("@c15t/core/modules/network-blocker")).createNetworkBlocker),createPersistence:l,createScriptLoader:h(B),createWindowDebug:d,preloadScriptLoader:e.__preloadScriptLoader?.(B),streamPrefetch:t?v(t):f,watchRevocationReload:c}},I=new WeakMap,J=new Set,K=(e,t)=>{if("hosted"in e){let o,r;return"hosted"in t&&(o=e.hosted,r=t.hosted,o.fetch===r.fetch&&o.initialData===r.initialData&&JSON.stringify(o)===JSON.stringify(r))}return"mode"in t&&e.fetch===t.fetch&&(e.mode===t.mode||e.mode.sameAs(t.mode))},F=function(o){if("function"!=typeof o)return o;let r=I.get(o);return r||(r=Object.assign(r=>e=(t=()=>o(Object.create(r,{iabEnabled:{value:void 0}})))(),{kind:o.kind}),I.set(o,r)),r},M=function(e){return{...e,mode:F(e.mode),pkg:e.__debugPkg??"@c15t/react"}},V=function(e){let{persistence:t}=e;if(!1===t||e.consentSource)return!1;let o="object"==typeof t?t:{};if(o.skipHydration)return!1;let r=o.now?o.now():Date.now(),{records:n}=a(o.storageConfig??e.storageConfig,r);return!!(n.choice||n.noticeDismissal)},W=function(e){let{journey:t}=e;return JSON.stringify({journey:t?[t.id,t.scope,t.storedChoice]:null,overrides:e.overrides,user:e.user})},z=function(e,t,o){if(!(o&&t.policyPending)||e.prefetch||e.experiment)return;let r=g(e.mode);if(r)return{source:{hosted:{...r,fetch:r.fetch??globalThis.fetch}},storedChoice:V(e)};let n=p.get(e.mode);if(!(!n||V(e))&&n.requestsInit(t.overrides))return{source:{fetch:globalThis.fetch,mode:n},storedChoice:!1}},H=new Set,G=0,Q=function(o,r){let n,i,s,{networkBlocker:c}=o,l=m(M(o),A(o)),a=o,d=!1,p=!1,h=0,f=e;G+=1;let g={expire(){J.delete(s),H.delete(g),clearTimeout(n),d=!0,l.dispose()},holdKey:!1!==o.enabled&&c&&!1!==c.enabled?JSON.stringify(c.rules):null,sequence:G};if("u"<typeof window)p=!0;else{H.add(g),n=setTimeout(g.expire,T);let e=l.kernel.getSnapshot(),c=z(o,e,r);if(c){let{source:r,storedChoice:n}=c,l=u({option:o.journey,storedChoice:n}),a=l?{journey:l,overrides:e.overrides,user:e.user}:{overrides:e.overrides,user:e.user},d=W(a);for(let e of J)e.key===d&&K(e.source,r)&&(i=e);if(!i){let e=t(),o=e.init,n=o(a);n.catch(()=>void 0);let c=!1;e.init=e=>{let t=!c&&W(e)===d;return c=!0,t?n:o(e)},i=s={key:d,source:r,transport:e},J.add(s)}}}let v=function(){if(d)return!1;if(p)return!0;if(p=!0,J.delete(i)&&Object.assign(f,i?.transport),H.delete(g),clearTimeout(n),null!==g.holdKey)for(let e of H)e.sequence<g.sequence&&e.holdKey===g.holdKey&&e.expire();return!0};return{apply(e){e!==a&&(a=e,l.update(M(e)).catch(()=>void 0))},mount(){if(!v())return null;let e=h+=1;return l.start(),()=>{queueMicrotask(()=>{h===e&&l.dispose()})}},runtime:l}},U=function(){let e=null;return{get:(t,o)=>e??=Q(t,o),reset(){e=null}}},X=e=>e+1,Y=()=>()=>void 0,Z=function(e){let[t]=x(U),[,o]=C(X,0),r=R(),n=k(()=>{t.reset(),o()},[t]);return e?{entry:t.get(e,r),rebuild:n}:void 0},$="u"<typeof document?S:b,ee=({entry:e,options:t,rebuild:o})=>($(()=>e.apply(t),[e,t]),S(()=>{let t=e.mount();return t||void o()},[e,o]),null),et=e=>{let{children:t}=e,c=e.options??{},[l]=x(()=>e.runtime),a=Z(l?void 0:c),d=a?.entry.runtime,u=l?e.runtime??l:void 0,m=d??u,p=j(d?d.subscribe:Y,()=>m.kernel,()=>m.kernel),[h]=x(()=>{var e;return l?l.experiment??s(c.experiment,(e=c.prefetch,"function"==typeof e?.then)?void 0:c.prefetch):void 0}),f=d?d.experiment:h,g=u?c.consentCategories:void 0;S(()=>{u&&void 0!==g&&u.setConsentCategories(g)},[u,g]);let{presentation:v}=c,k=w(()=>({clearRecords:()=>m.clearRecords(),getConsentCategories:()=>m.consentCategories,getPresentation:()=>n(v,f,p.getSnapshot().experiment),isStarted:()=>m.started,runtime:m,setLanguage:e=>m.setLanguage(e)}),[f,p,v,m]),b=!!u&&void 0!==c.callbacks;S(()=>{"production"!==process.env.NODE_ENV&&b&&console.warn("c15t ConsentProvider: `options.callbacks` is ignored when you pass `runtime`. Pass them to createConsentRuntime({ callbacks }) instead.")},[b]);let C=j(e=>p.subscribe(e),()=>p.getSnapshot().experiment,()=>p.getServerSnapshot().experiment),R=w(()=>i(c.theme,f,C),[c.theme,f,C]);S(()=>{"production"===process.env.NODE_ENV||!R||!["colors","dark","motion","radius","shadows","spacing","typography"].some(e=>e in R)||document.getElementById("c15t-theme")||console.warn("c15t: `theme` tokens are no longer turned into CSS in the browser. Render <ConsentTheme theme={theme} /> on the server, or put the CSS from generateThemeCSS() in your stylesheet. See https://c15t.com/docs/frameworks/react/customize")},[R]);let _=w(()=>({colorScheme:c.colorScheme,disableAnimation:c.disableAnimation,noStyle:c.noStyle,scrollLock:c.scrollLock,theme:R,trapFocus:c.trapFocus}),[R,c.noStyle,c.disableAnimation,c.scrollLock,c.trapFocus,c.colorScheme]),T=w(()=>({components:y(R?.slots,c.components,"className"),experiment:f,legalLinks:c.legalLinks,nonce:c.nonce,preloadDialog:c.preloadDialog,presentation:c.presentation,styles:c.styles,theme:c.theme}),[R?.slots,c.components,f,c.legalLinks,c.nonce,c.preloadDialog,c.presentation,c.styles,c.theme]);return D(c.colorScheme),o(L.Provider,{value:p,children:o(N.Provider,{value:k,children:r(E,{themeConfig:_,uiConfig:T,children:[a?o(ee,{entry:a.entry,options:c,rebuild:a.rebuild}):null,u?o(P,{runtime:u,children:t}):o(O.Provider,{value:!1===c.streamBanner?void 0:d?.streamed,children:t})]})})})};export{et as ConsentProvider};
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
let e="3.1.0
|
|
1
|
+
let e="3.1.0";export{e as version};
|
package/dist-types/context.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AllConsentNames, ConsentKernel, ConsentPresentation, ConsentSnapshot } from '@c15t/core';
|
|
2
|
-
import type { StreamedPrefetch } from '@c15t/core/runtime';
|
|
2
|
+
import type { ConsentRuntime, StreamedPrefetch } from '@c15t/core/runtime';
|
|
3
3
|
/**
|
|
4
4
|
* React context carrying the kernel instance.
|
|
5
5
|
*
|
|
@@ -16,6 +16,8 @@ export interface ProviderServices {
|
|
|
16
16
|
getConsentCategories: () => readonly AllConsentNames[];
|
|
17
17
|
/** Whether the runtime has started: records hydrated, prefetch adopted. */
|
|
18
18
|
isStarted: () => boolean;
|
|
19
|
+
/** The runtime the provider renders, owned or borrowed. */
|
|
20
|
+
runtime: ConsentRuntime;
|
|
19
21
|
/**
|
|
20
22
|
* Store a language override and load its copy: init runs again on the
|
|
21
23
|
* provider's own kernel while it is enabled, and a borrowed runtime
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { EventDispatcher, RuntimeEventDispatcherOptions } from '@c15t/core/events';
|
|
2
|
+
/**
|
|
3
|
+
* Get the provider's event dispatcher.
|
|
4
|
+
*
|
|
5
|
+
* It delivers to the scripts the provider registered, plus any you pass, and
|
|
6
|
+
* checks consent when it sends, so a revoked category stops the next event.
|
|
7
|
+
* The returned object stays the same for the provider's lifetime, and
|
|
8
|
+
* pageview de-duplication survives navigation, consent changes and the
|
|
9
|
+
* `enabled` toggle.
|
|
10
|
+
*
|
|
11
|
+
* @param options - Extra `scripts`, SPA `pageviews` integrations and
|
|
12
|
+
* injectable `globals`. The latest committed options apply on each call.
|
|
13
|
+
* @returns A dispatcher with `track` and `pageview`.
|
|
14
|
+
* @throws {Error} When called outside `ConsentProvider`.
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* const events = useEventDispatcher({ pageviews: ['posthog'] });
|
|
18
|
+
* const pathname = usePathname();
|
|
19
|
+
* useEffect(() => events.pageview(pathname), [events, pathname]);
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare const useEventDispatcher: (options?: RuntimeEventDispatcherOptions) => EventDispatcher;
|
|
23
|
+
export type { EventDispatcher, EventProperties } from '@c15t/core/events';
|
|
24
|
+
export type { RuntimeEventDispatcherOptions as UseEventDispatcherOptions };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One group of `@c15t/react`'s public exports. The package index re-exports
|
|
3
|
+
* each group with `export *`, so a bundler that splits code by reachability
|
|
4
|
+
* (esbuild) does not treat every module the index names as part of an app
|
|
5
|
+
* that imports one hook: only the groups it uses are live.
|
|
6
|
+
*/
|
|
7
|
+
export { useEventDispatcher } from '../event-dispatcher.js';
|
package/dist-types/index.d.ts
CHANGED
|
@@ -40,6 +40,7 @@ export type { ConsentDialogTriggerProps, ConsentDialogTriggerToolbarAction, Cons
|
|
|
40
40
|
export type { ConsentWidgetCompoundComponent, ConsentWidgetProps, } from './components/preferences/index.js';
|
|
41
41
|
export type { ConsentGateProps, FrameProps } from './components/consent-gate/index.js';
|
|
42
42
|
export type { ConsentDraftHandle, ConsentDraftProviderProps, VendorDraftHandle, } from './draft.js';
|
|
43
|
+
export type { EventDispatcher, EventProperties, UseEventDispatcherOptions, } from './event-dispatcher.js';
|
|
43
44
|
export type { UseIframeBlockerOptions, UseNetworkBlockerOptions, UsePersistenceOptions, UseScriptLoaderOptions, } from './module-hooks.js';
|
|
44
45
|
export type { ConsentProviderOptions, ConsentProviderCallbacks, ConsentProviderPrefetch, ConsentProviderProps, ExternalRuntimeProviderOptions, ExternalRuntimeProviderProps, OwnedRuntimeProviderProps, } from './provider.js';
|
|
45
46
|
export type { ConsentThemeProps } from './consent-theme.js';
|
|
@@ -58,6 +59,7 @@ export * from './index-parts/consent-dialog-trigger.js';
|
|
|
58
59
|
export * from './index-parts/consent-gate.js';
|
|
59
60
|
export * from './index-parts/frame.js';
|
|
60
61
|
export * from './index-parts/draft.js';
|
|
62
|
+
export * from './index-parts/event-dispatcher.js';
|
|
61
63
|
export * from './index-parts/translations.js';
|
|
62
64
|
export * from './index-parts/module-hooks.js';
|
|
63
65
|
export * from './index-parts/provider.js';
|
package/dist-types/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "3.1.0
|
|
1
|
+
export declare const version = "3.1.0";
|
|
@@ -136,6 +136,18 @@ writes the v3 format. The upgrade guides for
|
|
|
136
136
|
[JavaScript](https://c15t.com/docs/frameworks/javascript/upgrade-v3#keep-visitors-existing-choices)
|
|
137
137
|
cover how v3 treats v2 records.
|
|
138
138
|
|
|
139
|
+
## Retry interrupted saves
|
|
140
|
+
|
|
141
|
+
Before sending a recorded choice to the backend, c15t puts it in a localStorage
|
|
142
|
+
queue. If the page closes while the request is in flight, the next initialization
|
|
143
|
+
retries it with the original decision time. A successful response removes the
|
|
144
|
+
queued choice. A permanent rejection, such as a stale policy, removes it too.
|
|
145
|
+
|
|
146
|
+
Queueing depends on the browser allowing storage. When storage is unavailable,
|
|
147
|
+
c15t still attempts the request, but cannot preserve an interrupted request for
|
|
148
|
+
the next page. A locally remembered choice alone does not prove that the backend
|
|
149
|
+
received it; check the saved consent record in Inth.
|
|
150
|
+
|
|
139
151
|
## Keep open tabs in step
|
|
140
152
|
|
|
141
153
|
Tabs and windows on the same origin (scheme, host and port) share the consent
|
|
@@ -102,6 +102,50 @@ their vendor slug. See [vendor consent](./vendor-consent.md).
|
|
|
102
102
|
To send your own events only to allowed integrations, see
|
|
103
103
|
[send events only to allowed integrations](../../integrations/overview.md#send-events-only-to-allowed-integrations).
|
|
104
104
|
|
|
105
|
+
## Send your own analytics events
|
|
106
|
+
|
|
107
|
+
`useEventDispatcher()` returns the provider's event dispatcher. It sends to the
|
|
108
|
+
`scripts` you registered and checks consent when an event goes out. Call
|
|
109
|
+
`pageview` from your router with the current path:
|
|
110
|
+
|
|
111
|
+
```tsx title="src/analytics-events.tsx"
|
|
112
|
+
import { useEventDispatcher } from 'c15t/react';
|
|
113
|
+
import { useEffect } from 'react';
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Sends a PostHog pageview for each new path. Call it once, inside
|
|
117
|
+
* `ConsentProvider`, with your router's current path, such as
|
|
118
|
+
* `useLocation().pathname` from React Router.
|
|
119
|
+
*/
|
|
120
|
+
export const usePageviews = (path: string) => {
|
|
121
|
+
const events = useEventDispatcher({ pageviews: ['posthog'] });
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
events.pageview(path);
|
|
124
|
+
}, [events, path]);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export const SearchButton = ({ resultCount }: { resultCount: number }) => {
|
|
128
|
+
const events = useEventDispatcher();
|
|
129
|
+
return (
|
|
130
|
+
<button
|
|
131
|
+
type="button"
|
|
132
|
+
onClick={() => {
|
|
133
|
+
events.track('search', { resultCount });
|
|
134
|
+
}}
|
|
135
|
+
>
|
|
136
|
+
Search
|
|
137
|
+
</button>
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`track` sends to every registered script with an event API and drops the
|
|
143
|
+
event while measurement is denied. `pageview` sends only to the integrations
|
|
144
|
+
named in `pageviews`, and the first call records the path without sending.
|
|
145
|
+
Every dispatcher shares one pageview history for the provider's lifetime.
|
|
146
|
+
See [send events only to allowed integrations](../../integrations/overview.md#send-events-only-to-allowed-integrations)
|
|
147
|
+
for the supported vendors.
|
|
148
|
+
|
|
105
149
|
## Check the scripts
|
|
106
150
|
|
|
107
151
|
Open DevTools Network in a private window, filter by each vendor's domain and
|
|
@@ -156,33 +156,56 @@ and embeds group:
|
|
|
156
156
|
|
|
157
157
|
## Send events only to allowed integrations
|
|
158
158
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
159
|
+
The event dispatcher sends one named event to every registered integration
|
|
160
|
+
that has an event API and is allowed right now. It drops events while
|
|
161
|
+
measurement is denied, and one vendor's error does not stop delivery to the
|
|
162
|
+
others. Each framework gives you the provider's dispatcher, so you don't pass
|
|
163
|
+
the scripts or the consent snapshot yourself:
|
|
164
|
+
|
|
165
|
+
| Framework | Get the dispatcher |
|
|
166
|
+
| ------------------------------ | ------------------------------------------------------------------------------ |
|
|
167
|
+
| React, Next.js, TanStack Start | `useEventDispatcher()` from `c15t/react`, `c15t/next` or `c15t/tanstack-start` |
|
|
168
|
+
| Vue, Nuxt | `useEventDispatcher()` from `c15t/vue/vue-plugin`, auto-imported in Nuxt |
|
|
169
|
+
| Svelte, SvelteKit | `getEventDispatcher()` from `@c15t/svelte`, called inside `ConsentProvider` |
|
|
170
|
+
| Astro | `getEventDispatcher()` from `c15t/astro/client` |
|
|
171
|
+
| JavaScript | `getEventDispatcher(runtime)` from `@c15t/browser` |
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
import { useEventDispatcher } from 'c15t/react';
|
|
175
|
+
import { useEffect } from 'react';
|
|
176
|
+
|
|
177
|
+
export const DocsSearch = ({ path }: { path: string }) => {
|
|
178
|
+
const events = useEventDispatcher({ pageviews: ['posthog'] });
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
events.pageview(path);
|
|
181
|
+
}, [events, path]);
|
|
182
|
+
return (
|
|
183
|
+
<button onClick={() => events.track('docs_search', { resultCount: 4 })}>
|
|
184
|
+
Search
|
|
185
|
+
</button>
|
|
186
|
+
);
|
|
187
|
+
};
|
|
175
188
|
```
|
|
176
189
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
`
|
|
184
|
-
|
|
185
|
-
|
|
190
|
+
The dispatcher sends to the scripts registered on the provider. Pass
|
|
191
|
+
`scripts` to add more. It reads consent when an event goes out, so an event
|
|
192
|
+
sent right after a visitor withdraws consent is dropped. Every dispatcher for
|
|
193
|
+
the same provider shares one pageview history, so a component that remounts
|
|
194
|
+
or a consent change does not resend or lose a pageview.
|
|
195
|
+
|
|
196
|
+
`track` supports Google Tag Manager, Google Tag, PostHog, Mixpanel, Segment,
|
|
197
|
+
Hightouch, Heap, Amplitude, LogRocket, Adobe Analytics, Databuddy, Plausible,
|
|
198
|
+
Fathom, Pirsch, Microsoft Clarity, Hotjar, Vercel Analytics, OneDollarStats,
|
|
199
|
+
Umami, Rybbit, RudderStack and Matomo. `pageview` sends single-page-app page
|
|
200
|
+
views only to the integrations named in `pageviews`: PostHog, Segment or
|
|
201
|
+
Hightouch. Call it from your router's navigation hook. The first call records
|
|
202
|
+
the path without sending, and repeated paths or hash-only changes are skipped.
|
|
203
|
+
Each framework's scripts page shows where to call it.
|
|
204
|
+
|
|
205
|
+
Without a provider, `createEventDispatcher` from `@c15t/integrations/events`
|
|
206
|
+
takes `scripts` and a `getSnapshot` function that returns the live consent
|
|
207
|
+
snapshot. Create it once and keep it: a new dispatcher starts a new pageview
|
|
208
|
+
history.
|
|
186
209
|
|
|
187
210
|
## Add a vendor without a helper
|
|
188
211
|
|
|
@@ -276,6 +276,14 @@ A kernel you create yourself needs a loader from
|
|
|
276
276
|
| `'after-consent'` | Only while measurement is allowed | Calls `opt_in_capturing` at load and `opt_out_capturing` on revocation |
|
|
277
277
|
| `'disabled'` | Never | None. The helper returns an empty callback-only script |
|
|
278
278
|
|
|
279
|
+
With `'after-consent'`, a refusal can arrive while `array.js` is still
|
|
280
|
+
downloading. The download keeps going after c15t removes the script, so c15t
|
|
281
|
+
also clears PostHog's queued init. When `array.js` runs, it starts nothing, sends
|
|
282
|
+
no events and writes no cookies. If the visitor allows measurement again before
|
|
283
|
+
`array.js` runs, PostHog starts on the same page. If `array.js` has already
|
|
284
|
+
run, PostHog starts on the next page load. With the default
|
|
285
|
+
`reloadOnConsentRevoked`, the page reloads after the refusal anyway.
|
|
286
|
+
|
|
279
287
|
`posthog` uses the `measurement` category. With the default
|
|
280
288
|
`cookieless_mode: 'on_reject'`, PostHog keeps capturing without cookies after a
|
|
281
289
|
refusal. That mode needs cookieless server hashing turned on in your PostHog
|
package/package.json
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c15t/react",
|
|
3
|
-
"version": "3.1.0
|
|
4
|
-
"description": "React cookie banner, consent
|
|
3
|
+
"version": "3.1.0",
|
|
4
|
+
"description": "React cookie consent banner, consent dialog and preference center, plus headless hooks. A GDPR consent management platform (CMP) with Google Consent Mode v2 and IAB TCF.",
|
|
5
5
|
"keywords": [
|
|
6
|
+
"banner",
|
|
6
7
|
"ccpa",
|
|
7
8
|
"cmp",
|
|
8
9
|
"consent",
|
|
9
10
|
"consent-banner",
|
|
10
11
|
"consent-management",
|
|
11
12
|
"consent-management-platform",
|
|
13
|
+
"consent-manager",
|
|
12
14
|
"consent-manager-react",
|
|
13
15
|
"consent-mode-v2",
|
|
16
|
+
"cookie",
|
|
14
17
|
"cookie-banner",
|
|
15
18
|
"cookie-consent",
|
|
16
19
|
"gdpr",
|
|
20
|
+
"global-privacy-control",
|
|
17
21
|
"google-consent-mode",
|
|
18
22
|
"headless",
|
|
19
23
|
"iab",
|
|
24
|
+
"iab-tcf",
|
|
20
25
|
"lgpd",
|
|
21
26
|
"preference-center",
|
|
22
27
|
"privacy",
|
|
@@ -264,16 +269,16 @@
|
|
|
264
269
|
"test:watch": "bun prebuild && vitest"
|
|
265
270
|
},
|
|
266
271
|
"dependencies": {
|
|
267
|
-
"@c15t/core": "3.1.0
|
|
268
|
-
"@c15t/dev-tools": "3.1.0
|
|
269
|
-
"@c15t/iab": "3.1.0
|
|
270
|
-
"@c15t/schema": "3.1.0
|
|
271
|
-
"@c15t/translations": "3.1.0
|
|
272
|
-
"@c15t/ui": "3.0.1
|
|
272
|
+
"@c15t/core": "3.1.0",
|
|
273
|
+
"@c15t/dev-tools": "3.1.0",
|
|
274
|
+
"@c15t/iab": "3.1.0",
|
|
275
|
+
"@c15t/schema": "3.1.0",
|
|
276
|
+
"@c15t/translations": "3.1.0",
|
|
277
|
+
"@c15t/ui": "3.0.1",
|
|
273
278
|
"@types/google.maps": "3.65.3"
|
|
274
279
|
},
|
|
275
280
|
"devDependencies": {
|
|
276
|
-
"@c15t/backend": "3.1.0
|
|
281
|
+
"@c15t/backend": "3.1.0",
|
|
277
282
|
"@c15t/conformance": "0.0.1",
|
|
278
283
|
"@c15t/typescript-config": "0.0.1",
|
|
279
284
|
"@c15t/vitest-config": "1.0.0",
|