@c15t/react 3.1.0 → 3.2.0-canary.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.
Files changed (120) hide show
  1. package/AGENTS.md +11 -4
  2. package/README.md +2 -2
  3. package/dist/component-hooks/use-consent-gate.js +2 -0
  4. package/dist/component-hooks/use-consent-labels.js +2 -0
  5. package/dist/component-hooks/use-consent-text-direction.js +2 -0
  6. package/dist/component-hooks/use-vendors-listed-under.js +2 -0
  7. package/dist/components/consent-gate/atoms.js +1 -1
  8. package/dist/components/consent-gate/consent-gate.js +1 -1
  9. package/dist/components/iab-prompt/atoms/root.js +1 -1
  10. package/dist/components/panel-trigger/atoms/toolbar.js +1 -1
  11. package/dist/components/preferences/atoms/button.js +1 -1
  12. package/dist/components/preferences/atoms/vendor-list.js +1 -1
  13. package/dist/components/prompt/atoms/root.js +1 -1
  14. package/dist/components/prompt/components.js +1 -1
  15. package/dist/components/prompt/policy-actions.js +1 -1
  16. package/dist/components/prompt/prompt.js +1 -1
  17. package/dist/components/prompt/surface-context.js +1 -1
  18. package/dist/components/prompt/use-banner-copy.js +1 -1
  19. package/dist/components/shared/streamed-surface.js +1 -1
  20. package/dist/headless.js +1 -1
  21. package/dist/index-parts/offline.js +1 -1
  22. package/dist/modes.js +1 -1
  23. package/dist/transports/offline.js +1 -1
  24. package/dist/version.js +1 -1
  25. package/dist-types/component-hooks/use-consent-gate.d.ts +102 -0
  26. package/dist-types/component-hooks/use-consent-labels.d.ts +32 -0
  27. package/dist-types/component-hooks/use-consent-text-direction.d.ts +22 -0
  28. package/dist-types/component-hooks/use-vendors-listed-under.d.ts +30 -0
  29. package/dist-types/components/prompt/atoms/root.d.ts +3 -3
  30. package/dist-types/components/prompt/prompt.d.ts +4 -3
  31. package/dist-types/components/prompt/surface-context.d.ts +1 -14
  32. package/dist-types/components/prompt/use-banner-copy.d.ts +8 -3
  33. package/dist-types/components/shared/streamed-surface.d.ts +47 -10
  34. package/dist-types/headless.d.ts +10 -1
  35. package/dist-types/hooks/use-iab-policy-guard.d.ts +17 -1
  36. package/dist-types/hooks/use-scroll-lock.d.ts +16 -2
  37. package/dist-types/index-parts/offline.d.ts +1 -1
  38. package/dist-types/modes.d.ts +1 -1
  39. package/dist-types/module-hooks/script-loader.d.ts +9 -0
  40. package/dist-types/provider.d.ts +3 -3
  41. package/dist-types/transports/offline.d.ts +2 -2
  42. package/dist-types/version.d.ts +1 -1
  43. package/docs/README.md +11 -4
  44. package/docs/concepts/choose-your-setup.md +9 -9
  45. package/docs/concepts/consent-state.md +81 -9
  46. package/docs/concepts/data-fetching.md +30 -23
  47. package/docs/concepts/modes.md +85 -35
  48. package/docs/customization/recipes.md +4 -4
  49. package/docs/customization/stylesheets.md +3 -3
  50. package/docs/customization/tailwind.md +17 -34
  51. package/docs/customization/translations.md +18 -12
  52. package/docs/frameworks/react/clear-on-revocation.md +3 -2
  53. package/docs/frameworks/react/components/consent-banner.md +3 -3
  54. package/docs/frameworks/react/components/consent-provider.md +29 -29
  55. package/docs/frameworks/react/gpp.md +1 -1
  56. package/docs/frameworks/react/headless.md +146 -19
  57. package/docs/frameworks/react/iab.md +8 -1
  58. package/docs/frameworks/react/quickstart.md +1 -1
  59. package/docs/frameworks/react/rendering.md +1 -1
  60. package/docs/frameworks/react/translations.md +9 -9
  61. package/docs/frameworks/react/troubleshooting.md +1 -1
  62. package/docs/frameworks/react/upgrade-v3.md +39 -32
  63. package/docs/frameworks/react/vendor-consent.md +5 -0
  64. package/docs/guides/banner-experiments.md +3 -3
  65. package/docs/guides/troubleshooting.md +3 -2
  66. package/docs/integrations/adobe-analytics.md +2 -2
  67. package/docs/integrations/ahrefs-analytics.md +2 -2
  68. package/docs/integrations/amplitude.md +2 -2
  69. package/docs/integrations/clay.md +305 -0
  70. package/docs/integrations/clearbit.md +2 -2
  71. package/docs/integrations/cloudflare-web-analytics.md +2 -2
  72. package/docs/integrations/cloudflare-zaraz.md +2 -2
  73. package/docs/integrations/crisp.md +2 -2
  74. package/docs/integrations/databuddy.md +2 -2
  75. package/docs/integrations/fathom-analytics.md +2 -2
  76. package/docs/integrations/front-chat.md +2 -2
  77. package/docs/integrations/google-maps.md +2 -2
  78. package/docs/integrations/google-tag-manager.md +59 -2
  79. package/docs/integrations/google-tag.md +2 -2
  80. package/docs/integrations/heap.md +2 -2
  81. package/docs/integrations/hightouch.md +2 -2
  82. package/docs/integrations/hotjar.md +40 -26
  83. package/docs/integrations/intercom.md +2 -2
  84. package/docs/integrations/klaviyo.md +2 -2
  85. package/docs/integrations/linkedin-insights.md +2 -2
  86. package/docs/integrations/logrocket.md +2 -2
  87. package/docs/integrations/matomo-analytics.md +2 -2
  88. package/docs/integrations/meta-pixel.md +2 -2
  89. package/docs/integrations/microsoft-clarity.md +2 -2
  90. package/docs/integrations/microsoft-uet.md +2 -2
  91. package/docs/integrations/mixpanel-analytics.md +2 -2
  92. package/docs/integrations/one-dollar-stats.md +2 -2
  93. package/docs/integrations/openai-pixel.md +2 -2
  94. package/docs/integrations/other-ads-and-pixels.md +94 -0
  95. package/docs/integrations/other-analytics.md +108 -0
  96. package/docs/integrations/other-chat-and-support.md +112 -0
  97. package/docs/integrations/other-email-and-sms.md +156 -0
  98. package/docs/integrations/other-experience.md +222 -0
  99. package/docs/integrations/overview.md +55 -21
  100. package/docs/integrations/pinterest-tag.md +2 -2
  101. package/docs/integrations/pirsch.md +2 -2
  102. package/docs/integrations/plausible-analytics.md +2 -2
  103. package/docs/integrations/posthog.md +2 -2
  104. package/docs/integrations/promptwatch.md +2 -2
  105. package/docs/integrations/reddit-pixel.md +2 -2
  106. package/docs/integrations/rudderstack.md +2 -2
  107. package/docs/integrations/rybbit-analytics.md +2 -2
  108. package/docs/integrations/segment.md +2 -2
  109. package/docs/integrations/sentry.md +2 -2
  110. package/docs/integrations/shopify.md +323 -0
  111. package/docs/integrations/snapchat-pixel.md +2 -2
  112. package/docs/integrations/tiktok-pixel.md +2 -2
  113. package/docs/integrations/umami-analytics.md +2 -2
  114. package/docs/integrations/vercel-analytics.md +2 -2
  115. package/docs/integrations/vercel-speed-insights.md +7 -2
  116. package/docs/integrations/x-pixel.md +2 -2
  117. package/docs/integrations/youtube.md +2 -2
  118. package/package.json +10 -32
  119. package/dist/components/prompt/resolve-banner-primary-actions.js +0 -1
  120. package/dist-types/components/prompt/resolve-banner-primary-actions.d.ts +0 -14
@@ -173,7 +173,7 @@ pass them as a top-level prop:
173
173
 
174
174
  ```svelte title="src/App.svelte"
175
175
  <script lang="ts">
176
- import { ConsentProvider, manifest } from '@c15t/svelte';
176
+ import { ConsentProvider, manifest } from 'c15t/svelte';
177
177
  import { scripts } from './consent-scripts';
178
178
  </script>
179
179
 
@@ -192,7 +192,7 @@ handle and the layout load from the [SvelteKit quickstart](https://c15t.com/docs
192
192
 
193
193
  ```svelte title="src/routes/+layout.svelte"
194
194
  <script lang="ts">
195
- import { ConsentRoot } from '@c15t/svelte';
195
+ import { ConsentRoot } from 'c15t/svelte';
196
196
  import { scripts } from '../consent-scripts';
197
197
 
198
198
  let { children, data } = $props();
@@ -165,7 +165,7 @@ pass them as a top-level prop:
165
165
 
166
166
  ```svelte title="src/App.svelte"
167
167
  <script lang="ts">
168
- import { ConsentProvider, manifest } from '@c15t/svelte';
168
+ import { ConsentProvider, manifest } from 'c15t/svelte';
169
169
  import { scripts } from './consent-scripts';
170
170
  </script>
171
171
 
@@ -184,7 +184,7 @@ handle and the layout load from the [SvelteKit quickstart](https://c15t.com/docs
184
184
 
185
185
  ```svelte title="src/routes/+layout.svelte"
186
186
  <script lang="ts">
187
- import { ConsentRoot } from '@c15t/svelte';
187
+ import { ConsentRoot } from 'c15t/svelte';
188
188
  import { scripts } from '../consent-scripts';
189
189
 
190
190
  let { children, data } = $props();
@@ -166,7 +166,7 @@ pass them as a top-level prop:
166
166
 
167
167
  ```svelte title="src/App.svelte"
168
168
  <script lang="ts">
169
- import { ConsentProvider, manifest } from '@c15t/svelte';
169
+ import { ConsentProvider, manifest } from 'c15t/svelte';
170
170
  import { scripts } from './consent-scripts';
171
171
  </script>
172
172
 
@@ -185,7 +185,7 @@ handle and the layout load from the [SvelteKit quickstart](https://c15t.com/docs
185
185
 
186
186
  ```svelte title="src/routes/+layout.svelte"
187
187
  <script lang="ts">
188
- import { ConsentRoot } from '@c15t/svelte';
188
+ import { ConsentRoot } from 'c15t/svelte';
189
189
  import { scripts } from '../consent-scripts';
190
190
 
191
191
  let { children, data } = $props();
@@ -184,7 +184,7 @@ pass them as a top-level prop:
184
184
 
185
185
  ```svelte title="src/App.svelte"
186
186
  <script lang="ts">
187
- import { ConsentProvider, manifest } from '@c15t/svelte';
187
+ import { ConsentProvider, manifest } from 'c15t/svelte';
188
188
  import { scripts } from './consent-scripts';
189
189
  </script>
190
190
 
@@ -203,7 +203,7 @@ handle and the layout load from the [SvelteKit quickstart](https://c15t.com/docs
203
203
 
204
204
  ```svelte title="src/routes/+layout.svelte"
205
205
  <script lang="ts">
206
- import { ConsentRoot } from '@c15t/svelte';
206
+ import { ConsentRoot } from 'c15t/svelte';
207
207
  import { scripts } from '../consent-scripts';
208
208
 
209
209
  let { children, data } = $props();
@@ -0,0 +1,323 @@
1
+ ---
2
+ title: Shopify
3
+ description: Send c15t consent to Shopify's Customer Privacy API from a Hydrogen
4
+ storefront, so Shopify analytics wait for the visitor's choice and follow each
5
+ later change.
6
+ seoTitle: Shopify Hydrogen cookie consent with c15t
7
+ seoDescription: Use your c15t banner as a custom consent banner in Shopify
8
+ Hydrogen. The c15t Shopify bridge passes each choice to Shopify's Customer
9
+ Privacy API.
10
+ group: integrations
11
+ ---
12
+
13
+ The c15t Shopify bridge sends each consent choice to Shopify's Customer
14
+ Privacy API. Shopify's analytics, and the apps and pixels that read that API,
15
+ then follow your c15t banner instead of Shopify's own. The bridge loads no
16
+ script. Hydrogen's `ShopifyScripts` loads the Customer Privacy API, and the
17
+ bridge's `setup` function holds Hydrogen's analytics events until Shopify has
18
+ the visitor's choice.
19
+
20
+ The [Shopify example](https://github.com/c15t/c15t/tree/v3/examples/shopify-nextjs)
21
+ runs the code on this page in a Next.js app against mock.shop.
22
+
23
+ ## Add the bridge
24
+
25
+ Set up c15t with the [Next.js quickstart](https://c15t.com/docs/frameworks/next/quickstart)
26
+ first, then install the integrations package:
27
+
28
+ | Package manager | Command |
29
+ | :-------------- | :------------------------------- |
30
+ | npm | `npm install @c15t/integrations` |
31
+ | pnpm | `pnpm add @c15t/integrations` |
32
+ | yarn | `yarn add @c15t/integrations` |
33
+ | bun | `bun add @c15t/integrations` |
34
+
35
+ Create the bridge once per page and pass its `setup` to `ShopifyScripts` with
36
+ `mode: 'custom-banner'`. A second component feeds the bridge each c15t
37
+ snapshot:
38
+
39
+ ```tsx title="components/shopify-analytics.tsx"
40
+ 'use client';
41
+
42
+ import { createShopifyCustomerPrivacy } from '@c15t/integrations/shopify';
43
+ import type { ShopifyScriptsI18n, ShopifyScriptsShop } from '@shopify/hydrogen';
44
+ import { ShopifyScripts } from '@shopify/hydrogen/react';
45
+ import { useSnapshot } from 'c15t/next';
46
+ import { useRouter } from 'next/navigation';
47
+ import { useEffect } from 'react';
48
+
49
+ // One bridge per page. It sends each c15t choice to Shopify's Customer
50
+ // Privacy API, and its `setup` holds Hydrogen's analytics until Shopify has
51
+ // the visitor's choice.
52
+ const shopifyPrivacy = createShopifyCustomerPrivacy();
53
+
54
+ /** Hydrogen's scripts and analytics bus. Render it in the document head. */
55
+ export const ShopifyAnalytics = ({
56
+ i18n,
57
+ shop,
58
+ }: {
59
+ i18n: ShopifyScriptsI18n;
60
+ shop: ShopifyScriptsShop;
61
+ }) => {
62
+ const router = useRouter();
63
+ return (
64
+ <ShopifyScripts
65
+ analytics={{ channel: 'headless' }}
66
+ consent={{ mode: 'custom-banner', setup: shopifyPrivacy.setup }}
67
+ i18n={i18n}
68
+ // Shopify's scripts route storefront links through Next's router.
69
+ navigate={(url) => router.push(url)}
70
+ shop={shop}
71
+ />
72
+ );
73
+ };
74
+
75
+ /** Feeds each c15t snapshot to the bridge. Render it inside `ConsentRoot`. */
76
+ export const ShopifyConsentSync = () => {
77
+ const snapshot = useSnapshot();
78
+ useEffect(() => shopifyPrivacy.update(snapshot), [snapshot]);
79
+ return null;
80
+ };
81
+ ```
82
+
83
+ Render `ShopifyAnalytics` in the document head, as Hydrogen requires, with
84
+ your shop's details. Render `ShopifyConsentSync` inside `ConsentRoot`:
85
+
86
+ ```tsx title="app/layout.tsx"
87
+ import {
88
+ ConsentBanner,
89
+ ConsentDialog,
90
+ ConsentDialogLink,
91
+ ConsentRoot,
92
+ } from 'c15t/next';
93
+ import { resolveConsent } from 'c15t/next/server';
94
+ import { connection } from 'next/server';
95
+ import { Suspense } from 'react';
96
+ import type { ReactNode } from 'react';
97
+
98
+ import { PageViewTracker } from '@/components/page-view-tracker';
99
+ import {
100
+ ShopifyAnalytics,
101
+ ShopifyConsentSync,
102
+ } from '@/components/shopify-analytics';
103
+ import { SiteHeader } from '@/components/site-header';
104
+ import { StoreProviders } from '@/components/store-providers';
105
+ import { currency, i18n, shop } from '@/lib/shop';
106
+ import { getCart } from '@/lib/storefront';
107
+
108
+ import './globals.css';
109
+
110
+ const RootLayout = async ({ children }: { children: ReactNode }) => {
111
+ // The cart comes from the visitor's cookie, so every page renders per request.
112
+ await connection();
113
+
114
+ return (
115
+ <html lang="en">
116
+ <head>
117
+ {/* Hydrogen's scripts belong in the head, once per page. */}
118
+ <ShopifyAnalytics i18n={{ ...i18n, currency }} shop={shop} />
119
+ </head>
120
+ <body>
121
+ <ConsentRoot state={resolveConsent()}>
122
+ <ShopifyConsentSync />
123
+ <StoreProviders cart={getCart()}>
124
+ <SiteHeader />
125
+ <Suspense fallback={null}>
126
+ <PageViewTracker />
127
+ </Suspense>
128
+ {children}
129
+ </StoreProviders>
130
+ <ConsentBanner />
131
+ <ConsentDialog />
132
+ <footer className="site-footer">
133
+ <ConsentDialogLink>Privacy settings</ConsentDialogLink>
134
+ </footer>
135
+ </ConsentRoot>
136
+ </body>
137
+ </html>
138
+ );
139
+ };
140
+
141
+ export default RootLayout;
142
+ ```
143
+
144
+ ## Declare the categories
145
+
146
+ c15t's dialog offers only the categories that something on the page uses.
147
+ Shopify's scripts load through `ShopifyScripts`, not through c15t's `scripts`
148
+ option, so c15t cannot find them. Declare the categories the bridge maps, or
149
+ the dialog shows only Necessary and Shopify receives a denial for everything:
150
+
151
+ ```ts title="c15t.config.ts"
152
+ import { defineConsentConfig } from 'c15t/next';
153
+
154
+ export default defineConsentConfig({
155
+ options: {
156
+ // The categories the Shopify bridge maps. Shopify's scripts load through
157
+ // Hydrogen's `ShopifyScripts`, not `scripts`, so c15t can't discover them.
158
+ consentCategories: ['measurement', 'marketing', 'functionality'],
159
+ },
160
+ });
161
+ ```
162
+
163
+ The categories must also be in your policy's scope.
164
+
165
+ ## Serve the Storefront API route
166
+
167
+ The Customer Privacy API saves each choice by posting to the storefront's own
168
+ `/api/<version>/graphql.json`. Hydrogen's `handleShopifyRoutes` serves that
169
+ route, forwards it to your shop and keeps the cookies Shopify sets. In
170
+ Next.js, call it from `proxy.ts`:
171
+
172
+ ```ts title="proxy.ts"
173
+ import {
174
+ createShopifyRequestContext,
175
+ createStorefrontClient,
176
+ handleShopifyRoutes,
177
+ } from '@shopify/hydrogen';
178
+ import { NextResponse } from 'next/server';
179
+ import type { NextRequest } from 'next/server';
180
+
181
+ import { cartHandlers } from '@/lib/cart-handlers';
182
+ import { i18n, storeDomain } from '@/lib/shop';
183
+
184
+ // This store has no customer accounts, so nothing keeps a session.
185
+ const noSession = {
186
+ getSessionItem: () => undefined,
187
+ getSessionOrigin: () => '',
188
+ removeSessionItem: () => undefined,
189
+ setSessionItem: () => undefined,
190
+ };
191
+
192
+ /**
193
+ * Serves Hydrogen's routes: `/api/cart` for the cart, and the Storefront API
194
+ * at `/api/<version>/graphql.json`, where Shopify's Customer Privacy API
195
+ * saves each consent choice.
196
+ */
197
+ export const proxy = (request: NextRequest) => {
198
+ const requestContext = createShopifyRequestContext({ i18n, request });
199
+ const storefrontClient = createStorefrontClient({
200
+ config: { publicStorefrontToken: undefined, storeDomain },
201
+ requestContext,
202
+ type: 'public',
203
+ });
204
+
205
+ const shopifyRoute = handleShopifyRoutes({
206
+ handlers: [cartHandlers],
207
+ request,
208
+ requestContext,
209
+ sessionManager: noSession,
210
+ storefrontClient,
211
+ });
212
+ if (shopifyRoute) {
213
+ return shopifyRoute;
214
+ }
215
+
216
+ const response = NextResponse.next({
217
+ request: { headers: requestContext.getForwardedRequestHeaders() },
218
+ });
219
+ requestContext.applyResponseHeaders(response.headers);
220
+ return response;
221
+ };
222
+
223
+ export const config = {
224
+ matcher: ['/((?!_next/static|_next/image|_next/data|favicon.ico).*)'],
225
+ };
226
+ ```
227
+
228
+ The consent route needs no handlers. The example adds Hydrogen's cart
229
+ handlers for its cart, and its `lib/shop.ts` holds the store domain and
230
+ market. It has no customer accounts, so it passes a session manager that
231
+ stores nothing. Pass your own if your storefront signs customers in.
232
+
233
+ ## How categories map
234
+
235
+ | Shopify consent | c15t category |
236
+ | --------------- | --------------- |
237
+ | `analytics` | `measurement` |
238
+ | `marketing` | `marketing` |
239
+ | `preferences` | `functionality` |
240
+ | `sale_of_data` | `marketing` |
241
+
242
+ Each Shopify value is `true` only while its c15t category is allowed in the
243
+ effective permissions, which include policy restrictions. Pass `categories`
244
+ to change the mapping:
245
+
246
+ ```ts
247
+ const shopifyPrivacy = createShopifyCustomerPrivacy({
248
+ categories: { sale_of_data: 'measurement' },
249
+ });
250
+ ```
251
+
252
+ `toShopifyConsent(permissions, categories)` returns the same four values for
253
+ your own code.
254
+
255
+ ## When the bridge sends
256
+
257
+ Shopify records consent only when the visitor makes a choice, so the bridge
258
+ sends nothing until then, or while the policy is pending.
259
+
260
+ * **Opt-in or IAB policy:** `setup` waits until the visitor answers the
261
+ banner, including with Reject All, so Hydrogen holds events until then.
262
+ * **Opt-out policy:** `setup` resolves without recording a choice, and
263
+ Shopify applies its region defaults, including Global Privacy Control,
264
+ until the visitor saves a choice in c15t. A stale grant is the exception,
265
+ below.
266
+
267
+ After the first choice, the bridge sends each change once. When a visitor
268
+ revokes a category, c15t reloads the page and the bridge sends the stored
269
+ choice on load.
270
+
271
+ While the visitor has no current choice, Shopify may still hold grants from
272
+ an earlier one, for example after that choice expired or a policy change
273
+ asks again. If c15t now denies any of them, the bridge sends `false` for
274
+ those grants only, so Shopify keeps the other signals, including unset ones.
275
+ It counts grants the bridge sent and each `yes` from Shopify's
276
+ `currentVisitorConsent()`, and sends the revocation again if Shopify reports
277
+ a dropped grant again, for example after another tab granted it. If that
278
+ function isn't available, it assumes a choice c15t stored may have granted
279
+ anything. Under an opt-in or IAB policy, Hydrogen keeps holding events until
280
+ the visitor answers. Under opt-out, `setup` resolves once Shopify accepts the
281
+ revocation.
282
+
283
+ If the Customer Privacy API hasn't loaded yet, the bridge waits for
284
+ Shopify's `consentTrackingApiLoaded` or `shopifyCustomerPrivacyApiLoaded`
285
+ event.
286
+
287
+ `setup` resolves once Shopify has accepted the visitor's latest choice, and
288
+ Hydrogen then delivers the events that choice allows. If the visitor changes
289
+ their mind while the first request is in flight, `setup` waits for the newer
290
+ one. It never rejects, because Hydrogen stops calling `setup` after a
291
+ rejection. When Shopify rejects a choice, the bridge keeps events blocked and
292
+ sends it again after 1, 2 and 4 seconds, then on the next update. If the
293
+ policy turns pending meanwhile, the bridge drops those retries.
294
+
295
+ ## Options
296
+
297
+ | Option | Default | Behavior |
298
+ | ------------ | ------------------- | -------------------------------------------------------------------------------------------------------- |
299
+ | `categories` | The table above | Overrides the c15t category for any of the four Shopify values. |
300
+ | `onSync` | None | Called with what Shopify accepted: the four values for a choice, or the dropped grants for a revocation. |
301
+ | `onError` | Logs to the console | Called when Shopify rejects a choice or `onSync` throws. |
302
+
303
+ The bridge returns `update(snapshot)`, `connect(source)` and `setup()`.
304
+ `connect` follows anything with `getSnapshot` and `subscribe`, such as a c15t
305
+ kernel, and returns a function that stops following it.
306
+
307
+ ## Limits
308
+
309
+ * Checkout runs on Shopify's domain and doesn't render your storefront code.
310
+ Shopify reads the stored choice there.
311
+ * Shopify's analytics need a real shop. mock.shop accepts the consent request
312
+ but sets no consent cookie, so `currentVisitorConsent()` stays empty.
313
+ * The bridge maps categories only; it doesn't translate IAB TCF choices.
314
+
315
+ ## Verify the bridge
316
+
317
+ 1. In a private window with an opt-in policy, load the page. The network panel
318
+ shows no `graphql.json` request with `visitorConsent`.
319
+ 2. Click Reject All. One request goes out with every value `false`.
320
+ 3. Open the dialog, allow Marketing and save. One request goes out with
321
+ `marketing` and `saleOfData` set to `true`.
322
+ 4. On a real shop, run `window.Shopify.customerPrivacy.currentVisitorConsent()`
323
+ in the console and check it matches your choice.
@@ -163,7 +163,7 @@ pass them as a top-level prop:
163
163
 
164
164
  ```svelte title="src/App.svelte"
165
165
  <script lang="ts">
166
- import { ConsentProvider, manifest } from '@c15t/svelte';
166
+ import { ConsentProvider, manifest } from 'c15t/svelte';
167
167
  import { scripts } from './consent-scripts';
168
168
  </script>
169
169
 
@@ -182,7 +182,7 @@ handle and the layout load from the [SvelteKit quickstart](https://c15t.com/docs
182
182
 
183
183
  ```svelte title="src/routes/+layout.svelte"
184
184
  <script lang="ts">
185
- import { ConsentRoot } from '@c15t/svelte';
185
+ import { ConsentRoot } from 'c15t/svelte';
186
186
  import { scripts } from '../consent-scripts';
187
187
 
188
188
  let { children, data } = $props();
@@ -164,7 +164,7 @@ pass them as a top-level prop:
164
164
 
165
165
  ```svelte title="src/App.svelte"
166
166
  <script lang="ts">
167
- import { ConsentProvider, manifest } from '@c15t/svelte';
167
+ import { ConsentProvider, manifest } from 'c15t/svelte';
168
168
  import { scripts } from './consent-scripts';
169
169
  </script>
170
170
 
@@ -183,7 +183,7 @@ handle and the layout load from the [SvelteKit quickstart](https://c15t.com/docs
183
183
 
184
184
  ```svelte title="src/routes/+layout.svelte"
185
185
  <script lang="ts">
186
- import { ConsentRoot } from '@c15t/svelte';
186
+ import { ConsentRoot } from 'c15t/svelte';
187
187
  import { scripts } from '../consent-scripts';
188
188
 
189
189
  let { children, data } = $props();
@@ -165,7 +165,7 @@ pass them as a top-level prop:
165
165
 
166
166
  ```svelte title="src/App.svelte"
167
167
  <script lang="ts">
168
- import { ConsentProvider, manifest } from '@c15t/svelte';
168
+ import { ConsentProvider, manifest } from 'c15t/svelte';
169
169
  import { scripts } from './consent-scripts';
170
170
  </script>
171
171
 
@@ -184,7 +184,7 @@ handle and the layout load from the [SvelteKit quickstart](https://c15t.com/docs
184
184
 
185
185
  ```svelte title="src/routes/+layout.svelte"
186
186
  <script lang="ts">
187
- import { ConsentRoot } from '@c15t/svelte';
187
+ import { ConsentRoot } from 'c15t/svelte';
188
188
  import { scripts } from '../consent-scripts';
189
189
 
190
190
  let { children, data } = $props();
@@ -165,7 +165,7 @@ pass them as a top-level prop:
165
165
 
166
166
  ```svelte title="src/App.svelte"
167
167
  <script lang="ts">
168
- import { ConsentProvider, manifest } from '@c15t/svelte';
168
+ import { ConsentProvider, manifest } from 'c15t/svelte';
169
169
  import { scripts } from './consent-scripts';
170
170
  </script>
171
171
 
@@ -184,7 +184,7 @@ handle and the layout load from the [SvelteKit quickstart](https://c15t.com/docs
184
184
 
185
185
  ```svelte title="src/routes/+layout.svelte"
186
186
  <script lang="ts">
187
- import { ConsentRoot } from '@c15t/svelte';
187
+ import { ConsentRoot } from 'c15t/svelte';
188
188
  import { scripts } from '../consent-scripts';
189
189
 
190
190
  let { children, data } = $props();
@@ -169,7 +169,7 @@ pass them as a top-level prop:
169
169
 
170
170
  ```svelte title="src/App.svelte"
171
171
  <script lang="ts">
172
- import { ConsentProvider, manifest } from '@c15t/svelte';
172
+ import { ConsentProvider, manifest } from 'c15t/svelte';
173
173
  import { scripts } from './consent-scripts';
174
174
  </script>
175
175
 
@@ -188,7 +188,7 @@ handle and the layout load from the [SvelteKit quickstart](https://c15t.com/docs
188
188
 
189
189
  ```svelte title="src/routes/+layout.svelte"
190
190
  <script lang="ts">
191
- import { ConsentRoot } from '@c15t/svelte';
191
+ import { ConsentRoot } from 'c15t/svelte';
192
192
  import { scripts } from '../consent-scripts';
193
193
 
194
194
  let { children, data } = $props();
@@ -276,6 +276,11 @@ The collector holds one `beforeSend` callback. Pass yours through the
276
276
  `beforeSend` option. Calling `window.si('beforeSend', ...)` yourself replaces
277
277
  the helper's consent check.
278
278
 
279
+ A framework that starts allowed scripts from the server HTML starts the
280
+ collector before the app hydrates, and the helper registers its callback at
281
+ hydration. With a `beforeSend` option, the collector waits for hydration
282
+ instead, so your callback sees every report.
283
+
279
284
  Reports only cover page loads after consent. The collector reads buffered
280
285
  paint and navigation timings, but interactions before consent are missed. The
281
286
  helper does not set `data-route`, so Vercel groups reports by URL rather than
@@ -163,7 +163,7 @@ pass them as a top-level prop:
163
163
 
164
164
  ```svelte title="src/App.svelte"
165
165
  <script lang="ts">
166
- import { ConsentProvider, manifest } from '@c15t/svelte';
166
+ import { ConsentProvider, manifest } from 'c15t/svelte';
167
167
  import { scripts } from './consent-scripts';
168
168
  </script>
169
169
 
@@ -182,7 +182,7 @@ handle and the layout load from the [SvelteKit quickstart](https://c15t.com/docs
182
182
 
183
183
  ```svelte title="src/routes/+layout.svelte"
184
184
  <script lang="ts">
185
- import { ConsentRoot } from '@c15t/svelte';
185
+ import { ConsentRoot } from 'c15t/svelte';
186
186
  import { scripts } from '../consent-scripts';
187
187
 
188
188
  let { children, data } = $props();
@@ -240,7 +240,7 @@ The provider from your quickstart supplies its consent state.
240
240
 
241
241
  ```svelte title="src/ConsentEmbed.svelte"
242
242
  <script lang="ts">
243
- import { ConsentGate } from '@c15t/svelte';
243
+ import { ConsentGate } from 'c15t/svelte';
244
244
  import { embedCategory, embedURL, embedTitle, embedAspectRatio } from './embed-config';
245
245
  </script>
246
246
 
@@ -269,7 +269,7 @@ Keep the SvelteKit root and its server-resolved state unchanged.
269
269
 
270
270
  ```svelte title="src/lib/ConsentEmbed.svelte"
271
271
  <script lang="ts">
272
- import { ConsentGate } from '@c15t/svelte';
272
+ import { ConsentGate } from 'c15t/svelte';
273
273
  import { embedCategory, embedURL, embedTitle, embedAspectRatio } from '../embed-config';
274
274
  </script>
275
275
 
package/package.json CHANGED
@@ -1,41 +1,19 @@
1
1
  {
2
2
  "name": "@c15t/react",
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.",
3
+ "version": "3.2.0-canary.0",
4
+ "description": "React cookie consent banner and React consent dialog for c15t, plus a React cookie banner, preference center and headless hooks.",
5
5
  "keywords": [
6
- "banner",
7
- "ccpa",
8
6
  "cmp",
9
7
  "consent",
10
- "consent-banner",
11
- "consent-management",
12
- "consent-management-platform",
13
8
  "consent-manager",
14
- "consent-manager-react",
15
- "consent-mode-v2",
16
- "cookie",
17
9
  "cookie-banner",
18
10
  "cookie-consent",
19
11
  "gdpr",
20
- "global-privacy-control",
21
- "google-consent-mode",
22
12
  "headless",
23
- "iab",
24
13
  "iab-tcf",
25
- "lgpd",
26
- "preference-center",
27
- "privacy",
28
14
  "react",
29
- "react-cmp",
30
- "react-consent",
31
15
  "react-cookie-banner",
32
- "react-cookie-consent",
33
- "react-server-components",
34
- "rsc",
35
- "tcf",
36
- "tracking-consent",
37
- "typescript",
38
- "use-client"
16
+ "react-cookie-consent"
39
17
  ],
40
18
  "homepage": "https://c15t.com/docs/frameworks/react/quickstart",
41
19
  "bugs": {
@@ -269,16 +247,16 @@
269
247
  "test:watch": "bun prebuild && vitest"
270
248
  },
271
249
  "dependencies": {
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",
250
+ "@c15t/core": "3.2.0-canary.0",
251
+ "@c15t/dev-tools": "3.2.0-canary.0",
252
+ "@c15t/iab": "3.2.0-canary.0",
253
+ "@c15t/schema": "3.1.1-canary.0",
254
+ "@c15t/translations": "3.2.0-canary.0",
255
+ "@c15t/ui": "3.0.2-canary.0",
278
256
  "@types/google.maps": "3.65.3"
279
257
  },
280
258
  "devDependencies": {
281
- "@c15t/backend": "3.1.0",
259
+ "@c15t/backend": "3.2.0-canary.0",
282
260
  "@c15t/conformance": "0.0.1",
283
261
  "@c15t/typescript-config": "0.0.1",
284
262
  "@c15t/vitest-config": "1.0.0",
@@ -1 +0,0 @@
1
- let r=function(r,e){return 0===r.length&&1===e.length&&"dismiss"===e[0]?e:r};export{r as resolveBannerPrimaryActions};
@@ -1,14 +0,0 @@
1
- /**
2
- * Primary actions for the banner, with dismiss promoted on a bare notice.
3
- *
4
- * @remarks
5
- * The resolver's default primary is `customize`, which a notice never
6
- * offers, so a notice resolves with no primary action. When dismiss is the
7
- * only control on the surface it is the primary one.
8
- *
9
- * @param primaryActions - Primary actions from the resolved presentation.
10
- * @param orderedActions - Every action the surface renders, in order.
11
- * @returns The primary actions the banner should style.
12
- * @internal
13
- */
14
- export declare const resolveBannerPrimaryActions: <Action extends string>(primaryActions: readonly Action[], orderedActions: readonly Action[]) => readonly Action[];