@c15t/react 3.0.0-alpha.3 → 3.0.0-alpha.4

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 (183) hide show
  1. package/AGENTS.md +91 -72
  2. package/README.md +21 -17
  3. package/SKILL.md +34 -0
  4. package/dist/component-hooks/use-headless-consent-ui.js +1 -1
  5. package/dist/components/consent-gate/atoms.js +1 -1
  6. package/dist/components/consent-gate/consent-gate.js +1 -1
  7. package/dist/components/iab-panel/atoms/card.js +1 -1
  8. package/dist/components/iab-panel/atoms/feature-item.js +2 -0
  9. package/dist/components/iab-panel/atoms/header.js +1 -1
  10. package/dist/components/iab-panel/iab-panel.js +1 -1
  11. package/dist/components/iab-panel/index.js +1 -1
  12. package/dist/components/iab-prompt/atoms/overlay.js +1 -1
  13. package/dist/components/iab-prompt/atoms/root.js +1 -1
  14. package/dist/components/panel/atoms/card.js +1 -1
  15. package/dist/components/panel/atoms/root.js +1 -1
  16. package/dist/components/panel-trigger/atoms/button.js +1 -1
  17. package/dist/components/panel-trigger/atoms/toolbar.js +1 -1
  18. package/dist/components/preferences/atoms/accordion.js +1 -1
  19. package/dist/components/prompt/atoms/overlay.js +1 -1
  20. package/dist/components/prompt/atoms/root.js +1 -1
  21. package/dist/components/prompt/components.js +1 -1
  22. package/dist/components/shared/libs/compose-refs.js +1 -0
  23. package/dist/components/shared/libs/slot.js +1 -1
  24. package/dist/components/shared/primitives/button.js +1 -1
  25. package/dist/components/shared/ui/dialog/dialog.js +1 -1
  26. package/dist/components/shared/ui/icon.js +1 -1
  27. package/dist/context/consent-tracking-context.js +1 -1
  28. package/dist/devtools.js +1 -1
  29. package/dist/draft.js +1 -1
  30. package/dist/hooks/use-focus-trap.js +1 -1
  31. package/dist/hooks.js +1 -1
  32. package/dist/iab/styles.tw3.css +4 -4
  33. package/dist/index.js +1 -1
  34. package/dist/module-hooks/iframe-blocker.js +1 -1
  35. package/dist/postcss-tailwind3.js +1 -0
  36. package/dist/provider.js +1 -1
  37. package/dist/server/fetch-ssr-data.js +1 -1
  38. package/dist/server/headers.js +1 -1
  39. package/dist/server/normalize-url.js +1 -1
  40. package/dist/styles.tw3.css +16 -16
  41. package/dist/transports/offline.js +1 -1
  42. package/dist/ui-save.js +1 -1
  43. package/dist/utils/merge-slot-props.js +1 -1
  44. package/dist/utils/theme-utils.js +1 -1
  45. package/dist/version.js +1 -1
  46. package/dist-types/component-hooks/use-headless-consent-ui.d.ts +1 -1
  47. package/dist-types/components/iab-panel/atoms/feature-item.d.ts +28 -0
  48. package/dist-types/components/iab-panel/iab-panel.d.ts +2 -2
  49. package/dist-types/components/iab-panel/index.d.ts +5 -2
  50. package/dist-types/components/iab-panel/use-iab-translations.d.ts +4 -0
  51. package/dist-types/components/iab-prompt/iab-prompt.d.ts +2 -2
  52. package/dist-types/components/iab-prompt/index.d.ts +2 -2
  53. package/dist-types/components/panel/atoms/card.d.ts +1 -0
  54. package/dist-types/components/prompt/components.d.ts +6 -0
  55. package/dist-types/components/shared/libs/compose-refs.d.ts +20 -0
  56. package/dist-types/components/shared/ui/icon.d.ts +2 -1
  57. package/dist-types/context/consent-tracking-context.d.ts +10 -0
  58. package/dist-types/context/theme-context.d.ts +2 -2
  59. package/dist-types/context.d.ts +6 -0
  60. package/dist-types/devtools.d.ts +13 -2
  61. package/dist-types/draft.d.ts +10 -4
  62. package/dist-types/hooks/use-focus-trap.d.ts +3 -1
  63. package/dist-types/hooks.d.ts +54 -13
  64. package/dist-types/iab.d.ts +1 -1
  65. package/dist-types/index.d.ts +2 -2
  66. package/dist-types/postcss-tailwind3.d.ts +21 -0
  67. package/dist-types/provider.d.ts +22 -5
  68. package/dist-types/server/headers.d.ts +14 -4
  69. package/dist-types/server/normalize-url.d.ts +14 -7
  70. package/dist-types/server/types.d.ts +12 -1
  71. package/dist-types/transports/offline.d.ts +7 -16
  72. package/dist-types/ui-config-context.d.ts +7 -1
  73. package/dist-types/utils/merge-slot-props.d.ts +15 -1
  74. package/dist-types/utils/theme-utils.d.ts +17 -2
  75. package/dist-types/version.d.ts +1 -1
  76. package/docs/README.md +91 -72
  77. package/docs/assets/v3/bottom-bar.png +0 -0
  78. package/docs/assets/v3/brand-card.png +0 -0
  79. package/docs/assets/v3/brand-preferences.png +0 -0
  80. package/docs/assets/v3/choice-wall.png +0 -0
  81. package/docs/assets/v3/headless-bar-html.png +0 -0
  82. package/docs/assets/v3/headless-bar-mobile.png +0 -0
  83. package/docs/assets/v3/headless-bar.png +0 -0
  84. package/docs/assets/v3/slim-bar.png +0 -0
  85. package/docs/concepts/choose-your-setup.md +87 -0
  86. package/docs/{frameworks/react/concepts → concepts}/consent-categories.md +22 -7
  87. package/docs/{guides → concepts}/consent-state.md +71 -101
  88. package/docs/{guides → concepts}/data-fetching.md +31 -27
  89. package/docs/concepts/how-consent-works.md +123 -0
  90. package/docs/{frameworks/react/concepts/policy-presets.md → concepts/policies.md} +7 -59
  91. package/docs/customization/class-names.md +202 -0
  92. package/docs/customization/dark-mode.md +157 -0
  93. package/docs/customization/motion.md +119 -0
  94. package/docs/customization/overview.md +67 -34
  95. package/docs/customization/recipes.md +839 -49
  96. package/docs/customization/slots.md +216 -35
  97. package/docs/customization/stylesheets.md +147 -0
  98. package/docs/customization/tailwind.md +842 -0
  99. package/docs/customization/tokens.md +163 -96
  100. package/docs/customization/translations.md +61 -3
  101. package/docs/frameworks/react/callbacks.md +118 -0
  102. package/docs/frameworks/react/clear-on-revocation.md +106 -0
  103. package/docs/frameworks/react/components/consent-banner.md +42 -28
  104. package/docs/frameworks/react/components/consent-dialog-link.md +13 -11
  105. package/docs/frameworks/react/components/consent-dialog-trigger.md +81 -76
  106. package/docs/frameworks/react/components/consent-dialog.md +56 -50
  107. package/docs/frameworks/react/components/consent-gate.md +37 -20
  108. package/docs/frameworks/react/components/consent-provider.md +126 -0
  109. package/docs/frameworks/react/components/consent-widget.md +23 -20
  110. package/docs/frameworks/react/components/dev-tools.md +52 -84
  111. package/docs/frameworks/react/components.md +105 -0
  112. package/docs/frameworks/react/compose.md +354 -0
  113. package/docs/frameworks/react/content-security-policy.md +126 -0
  114. package/docs/frameworks/react/customize.md +250 -0
  115. package/docs/frameworks/react/embeds.md +89 -0
  116. package/docs/frameworks/react/{hooks/overview.md → hooks.md} +21 -16
  117. package/docs/frameworks/react/{iab/overview.md → iab.md} +80 -31
  118. package/docs/frameworks/react/network-blocker.md +35 -41
  119. package/docs/frameworks/react/quickstart.md +139 -101
  120. package/docs/frameworks/react/rendering.md +128 -0
  121. package/docs/frameworks/react/scripts.md +115 -0
  122. package/docs/frameworks/react/translations.md +155 -0
  123. package/docs/frameworks/react/troubleshooting.md +92 -47
  124. package/docs/frameworks/react/vendor-consent.md +170 -0
  125. package/docs/guides/banner-experiments.md +654 -0
  126. package/docs/guides/troubleshooting.md +120 -47
  127. package/docs/guides/verify-consent.md +81 -49
  128. package/docs/integrations/adobe-analytics.md +167 -159
  129. package/docs/integrations/ahrefs-analytics.md +152 -154
  130. package/docs/integrations/amplitude.md +162 -156
  131. package/docs/integrations/building-integrations.md +136 -37
  132. package/docs/integrations/clearbit.md +154 -154
  133. package/docs/integrations/cloudflare-web-analytics.md +156 -156
  134. package/docs/integrations/cloudflare-zaraz.md +209 -261
  135. package/docs/integrations/crisp.md +164 -158
  136. package/docs/integrations/databuddy.md +157 -173
  137. package/docs/integrations/fathom-analytics.md +158 -156
  138. package/docs/integrations/front-chat.md +167 -167
  139. package/docs/integrations/google-maps.md +118 -83
  140. package/docs/integrations/google-tag-manager.md +178 -163
  141. package/docs/integrations/google-tag.md +163 -160
  142. package/docs/integrations/heap.md +163 -155
  143. package/docs/integrations/hightouch.md +161 -157
  144. package/docs/integrations/hotjar.md +159 -155
  145. package/docs/integrations/intercom.md +183 -153
  146. package/docs/integrations/klaviyo.md +486 -0
  147. package/docs/integrations/linkedin-insights.md +174 -150
  148. package/docs/integrations/logrocket.md +160 -156
  149. package/docs/integrations/matomo-analytics.md +188 -178
  150. package/docs/integrations/meta-pixel.md +188 -150
  151. package/docs/integrations/microsoft-clarity.md +163 -155
  152. package/docs/integrations/microsoft-uet.md +148 -154
  153. package/docs/integrations/mixpanel-analytics.md +155 -160
  154. package/docs/integrations/one-dollar-stats.md +166 -165
  155. package/docs/integrations/openai-pixel.md +204 -301
  156. package/docs/integrations/overview.md +137 -80
  157. package/docs/integrations/pinterest-tag.md +191 -183
  158. package/docs/integrations/pirsch.md +169 -159
  159. package/docs/integrations/plausible-analytics.md +172 -158
  160. package/docs/integrations/posthog.md +226 -241
  161. package/docs/integrations/promptwatch.md +154 -154
  162. package/docs/integrations/reddit-pixel.md +185 -157
  163. package/docs/integrations/rudderstack.md +201 -186
  164. package/docs/integrations/rybbit-analytics.md +171 -160
  165. package/docs/integrations/segment.md +182 -154
  166. package/docs/integrations/snapchat-pixel.md +186 -156
  167. package/docs/integrations/tiktok-pixel.md +171 -150
  168. package/docs/integrations/umami-analytics.md +163 -158
  169. package/docs/integrations/vercel-analytics.md +166 -157
  170. package/docs/integrations/x-pixel.md +176 -150
  171. package/docs/integrations/youtube.md +121 -86
  172. package/docs/upgrade-v3.md +475 -467
  173. package/package.json +15 -9
  174. package/docs/assets/v3/brand-bar.png +0 -0
  175. package/docs/assets/v3/mobile-card.png +0 -0
  176. package/docs/assets/v3/preferences.png +0 -0
  177. package/docs/frameworks/react/components/consent-manager-provider.md +0 -95
  178. package/docs/frameworks/react/script-loader.md +0 -69
  179. package/docs/frameworks/react/styling/overview.md +0 -266
  180. package/docs/guides/deployment-modes.md +0 -75
  181. package/docs/guides/shared-consent-controls.md +0 -158
  182. package/docs/integrations/clear-on-revocation.md +0 -167
  183. package/docs/integrations/granular-consent.md +0 -210
@@ -1,6 +1,9 @@
1
1
  ---
2
2
  title: Google Maps
3
- description: Prevent a map iframe from mounting before the required permission.
3
+ description: Gate a Google Maps iframe embed with c15t v3 so the map loads only
4
+ after the visitor allows its consent category, in Next.js, TanStack Start,
5
+ React, Nuxt, Vue, Astro, Svelte, SvelteKit, HTML or JavaScript.
6
+ icon: google-maps
4
7
  group: integrations
5
8
  ---
6
9
 
@@ -11,7 +14,8 @@ application; this example uses functionality permission.
11
14
 
12
15
  ```ts title="src/embed-config.ts"
13
16
  export const embedCategory = 'functionality' as const;
14
- export const embedURL = 'https://www.google.com/maps/embed?pb=YOUR_EMBED_PARAMETERS';
17
+ export const embedURL =
18
+ 'https://www.google.com/maps/embed?pb=YOUR_EMBED_PARAMETERS';
15
19
  export const embedTitle = 'Map showing our office location';
16
20
  export const embedAspectRatio = '4 / 3';
17
21
  ```
@@ -107,64 +111,66 @@ on revocation. Keep your existing consent styles and preferences dialog.
107
111
 
108
112
  **Nuxt**
109
113
 
110
- Use the reactive snapshot from the existing consent runtime. The Nuxt module auto-imports the consent composables.
114
+ The Nuxt module registers `ConsentGate` and `ConsentPreferencesLink`, so
115
+ this component imports only the embed configuration.
111
116
 
112
117
  ```vue title="app/components/ConsentEmbed.vue"
113
118
  <script setup lang="ts">
114
119
  import { embedCategory, embedURL, embedTitle, embedAspectRatio } from '../../src/embed-config';
115
-
116
- const snapshot = useConsentSnapshot();
117
- const activeUI = useConsentActiveUI();
118
120
  </script>
119
121
 
120
122
  <template>
121
- <iframe
122
- v-if="snapshot.effectivePermissions[embedCategory]"
123
- :src="embedURL"
124
- :title="embedTitle"
125
- loading="lazy"
126
- allowfullscreen
127
- :style="{ width: '100%', aspectRatio: embedAspectRatio, minHeight: '200px', border: 0 }"
128
- />
129
- <button v-else type="button" @click="activeUI = 'manager'">
130
- Open privacy settings to view this content
131
- </button>
123
+ <ConsentGate :category="embedCategory">
124
+ <iframe
125
+ :src="embedURL"
126
+ :title="embedTitle"
127
+ loading="lazy"
128
+ allowfullscreen
129
+ :style="{ width: '100%', aspectRatio: embedAspectRatio, minHeight: '200px', border: 0 }"
130
+ />
131
+ <template #placeholder>
132
+ <p>Allow {{ embedCategory }} to load this content.</p>
133
+ <ConsentPreferencesLink>Open privacy settings</ConsentPreferencesLink>
134
+ </template>
135
+ </ConsentGate>
132
136
  </template>
133
137
  ```
134
138
 
135
- Use `v-if` so a denied iframe is removed from the DOM. Hiding an existing
136
- iframe with `v-show` or CSS does not prevent its requests.
139
+ `ConsentGate` keeps the iframe out of the server HTML and the DOM while
140
+ permission is denied, and removes it on revocation. See
141
+ [Nuxt scripts and embeds](https://c15t.com/docs/frameworks/nuxt/scripts).
137
142
 
138
143
  **Vue**
139
144
 
140
- Use the reactive snapshot from the existing consent runtime. The c15t Vue plugin must already be installed on this app.
145
+ Render this component inside the app that installed the c15t Vue plugin.
141
146
 
142
147
  ```vue title="src/ConsentEmbed.vue"
143
148
  <script setup lang="ts">
144
- import { useConsentSnapshot, useConsentActiveUI } from 'c15t/vue/vue-plugin';
149
+ import ConsentGate from 'c15t/vue/runtime/components/consent-gate.vue';
150
+ import ConsentPreferencesLink from 'c15t/vue/runtime/components/consent-preferences-link.vue';
145
151
  import { embedCategory, embedURL, embedTitle, embedAspectRatio } from './embed-config';
146
-
147
- const snapshot = useConsentSnapshot();
148
- const activeUI = useConsentActiveUI();
149
152
  </script>
150
153
 
151
154
  <template>
152
- <iframe
153
- v-if="snapshot.effectivePermissions[embedCategory]"
154
- :src="embedURL"
155
- :title="embedTitle"
156
- loading="lazy"
157
- allowfullscreen
158
- :style="{ width: '100%', aspectRatio: embedAspectRatio, minHeight: '200px', border: 0 }"
159
- />
160
- <button v-else type="button" @click="activeUI = 'manager'">
161
- Open privacy settings to view this content
162
- </button>
155
+ <ConsentGate :category="embedCategory">
156
+ <iframe
157
+ :src="embedURL"
158
+ :title="embedTitle"
159
+ loading="lazy"
160
+ allowfullscreen
161
+ :style="{ width: '100%', aspectRatio: embedAspectRatio, minHeight: '200px', border: 0 }"
162
+ />
163
+ <template #placeholder>
164
+ <p>Allow {{ embedCategory }} to load this content.</p>
165
+ <ConsentPreferencesLink>Open privacy settings</ConsentPreferencesLink>
166
+ </template>
167
+ </ConsentGate>
163
168
  </template>
164
169
  ```
165
170
 
166
- Use `v-if` so a denied iframe is removed from the DOM. Hiding an existing
167
- iframe with `v-show` or CSS does not prevent its requests.
171
+ `ConsentGate` keeps the iframe out of the DOM while permission is denied,
172
+ and removes it on revocation. See
173
+ [Vue scripts and embeds](https://c15t.com/docs/frameworks/vue/scripts).
168
174
 
169
175
  **Astro**
170
176
 
@@ -273,16 +279,37 @@ The Svelte `ConsentGate` waits until the browser is mounted and the category is
273
279
  allowed. Its default placeholder opens preferences. Revocation removes the
274
280
  iframe.
275
281
 
282
+ **HTML**
283
+
284
+ The c15t script tag gates iframes that name a category. Put the embed's URL
285
+ in `data-src` instead of `src`, using the values from the configuration on
286
+ this page:
287
+
288
+ ```html
289
+ <iframe
290
+ data-src="https://www.youtube-nocookie.com/embed/VIDEO_ID?playsinline=1"
291
+ data-category="measurement"
292
+ title="Product video"
293
+ loading="lazy"
294
+ allowfullscreen
295
+ ></iframe>
296
+ ```
297
+
298
+ c15t sets `src` once the category is allowed and removes it on revocation.
299
+ Without `src` the iframe loads nothing, so hide it with CSS and show a link
300
+ to `#c15t-preferences` in its place. See
301
+ [HTML embeds](https://c15t.com/docs/frameworks/html/embeds).
302
+
276
303
  **JavaScript**
277
304
 
278
- Use the shared browser helper below with your existing kernel. Put an
305
+ Use the shared browser helper below with your existing client. Put an
279
306
  empty container where the embed should appear:
280
307
 
281
308
  ```html
282
309
  <div id="consent-embed"></div>
283
310
  ```
284
311
 
285
- In your browser entry point, after creating the kernel:
312
+ In your browser entry point, after `init()`:
286
313
 
287
314
  ```ts
288
315
  import { mountConsentEmbed } from './consent-embed';
@@ -290,13 +317,16 @@ import { mountConsentEmbed } from './consent-embed';
290
317
  const container = document.querySelector<HTMLElement>('#consent-embed');
291
318
  if (!container) throw new Error('Missing consent embed container');
292
319
 
293
- const disposeEmbed = mountConsentEmbed(container, kernel, openPreferences);
320
+ const disposeEmbed = mountConsentEmbed(container, consent.kernel, () =>
321
+ consent.openDialog(),
322
+ );
294
323
  ```
295
324
 
296
- `kernel` is the instance from your quickstart. `openPreferences` is your
297
- application's function for showing its consent preferences UI. Call
298
- `disposeEmbed()` when the page or component is destroyed. The helper observes
299
- both the current snapshot and later changes.
325
+ `consent` is the client from `init()` in your quickstart. With
326
+ `createConsentRuntime`, pass `runtime.kernel` and your own function that
327
+ opens preferences. Call `disposeEmbed()` when the page or component is
328
+ destroyed. Iframe markup with `data-src` and `data-category` also works
329
+ without the helper, because both setups gate iframes by default.
300
330
 
301
331
  ## Browser helper for Astro and JavaScript
302
332
 
@@ -305,51 +335,56 @@ when permission allows it, keeps an existing player mounted across unrelated
305
335
  snapshot updates, and removes it on revocation.
306
336
 
307
337
  ```ts title="src/consent-embed.ts"
308
- import { embedCategory, embedURL, embedTitle, embedAspectRatio } from './embed-config';
338
+ import {
339
+ embedCategory,
340
+ embedURL,
341
+ embedTitle,
342
+ embedAspectRatio,
343
+ } from './embed-config';
309
344
 
310
345
  type EmbedKernel = {
311
- getSnapshot: () => {
312
- effectivePermissions: Record<typeof embedCategory, boolean>;
313
- };
314
- subscribe: (listener: () => void) => () => void;
346
+ getSnapshot: () => {
347
+ effectivePermissions: Record<typeof embedCategory, boolean>;
348
+ };
349
+ subscribe: (listener: () => void) => () => void;
315
350
  };
316
351
 
317
352
  export function mountConsentEmbed(
318
- container: HTMLElement,
319
- kernel: EmbedKernel,
320
- openPreferences: () => void,
353
+ container: HTMLElement,
354
+ kernel: EmbedKernel,
355
+ openPreferences: () => void
321
356
  ) {
322
- const render = () => {
323
- if (kernel.getSnapshot().effectivePermissions[embedCategory]) {
324
- if (container.querySelector('iframe')) return;
325
- const frame = document.createElement('iframe');
326
- frame.src = embedURL;
327
- frame.title = embedTitle;
328
- frame.loading = 'lazy';
329
- frame.allowFullscreen = true;
330
- Object.assign(frame.style, {
331
- width: '100%',
332
- aspectRatio: embedAspectRatio,
333
- minHeight: '200px',
334
- border: '0',
335
- });
336
- container.replaceChildren(frame);
337
- } else {
338
- if (container.querySelector('button')) return;
339
- const button = document.createElement('button');
340
- button.type = 'button';
341
- button.textContent = 'Open privacy settings to view this content';
342
- button.onclick = openPreferences;
343
- container.replaceChildren(button);
344
- }
345
- };
346
-
347
- render();
348
- const unsubscribe = kernel.subscribe(render);
349
- return () => {
350
- unsubscribe();
351
- container.replaceChildren();
352
- };
357
+ const render = () => {
358
+ if (kernel.getSnapshot().effectivePermissions[embedCategory]) {
359
+ if (container.querySelector('iframe')) return;
360
+ const frame = document.createElement('iframe');
361
+ frame.src = embedURL;
362
+ frame.title = embedTitle;
363
+ frame.loading = 'lazy';
364
+ frame.allowFullscreen = true;
365
+ Object.assign(frame.style, {
366
+ width: '100%',
367
+ aspectRatio: embedAspectRatio,
368
+ minHeight: '200px',
369
+ border: '0',
370
+ });
371
+ container.replaceChildren(frame);
372
+ } else {
373
+ if (container.querySelector('button')) return;
374
+ const button = document.createElement('button');
375
+ button.type = 'button';
376
+ button.textContent = 'Open privacy settings to view this content';
377
+ button.onclick = openPreferences;
378
+ container.replaceChildren(button);
379
+ }
380
+ };
381
+
382
+ render();
383
+ const unsubscribe = kernel.subscribe(render);
384
+ return () => {
385
+ unsubscribe();
386
+ container.replaceChildren();
387
+ };
353
388
  }
354
389
  ```
355
390