@c15t/react 3.0.0-alpha.2 → 3.0.0-alpha.3
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/AGENTS.md +6 -1
- package/dist/aggregate-components.js +1 -1
- package/dist/chunk-warming.js +1 -1
- package/dist/component-hooks/use-headless-iab-consent-ui.js +1 -1
- package/dist/component-hooks/use-panel-trigger.js +1 -1
- package/dist/components/consent-gate/consent-gate.js +1 -1
- package/dist/components/consent-preferences-link/consent-preferences-link.js +1 -1
- package/dist/components/iab-panel/atoms/purpose-item.js +1 -1
- package/dist/components/iab-panel/atoms/stack-item.js +1 -1
- package/dist/components/iab-panel/iab-panel.js +1 -1
- package/dist/components/iab-panel/index.js +1 -1
- package/dist/components/panel/atoms/overlay.js +1 -1
- package/dist/components/panel/atoms/root.js +1 -1
- package/dist/components/panel/index.js +1 -1
- package/dist/components/panel/panel.js +1 -1
- package/dist/components/panel-trigger/atoms/button.js +1 -1
- package/dist/components/panel-trigger/atoms/icon.js +1 -1
- package/dist/components/panel-trigger/atoms/root.js +1 -1
- package/dist/components/panel-trigger/atoms/trigger-icons.js +1 -0
- package/dist/components/panel-trigger/panel-trigger-toolbar.js +1 -1
- package/dist/components/preferences/atoms/accordion.js +1 -1
- package/dist/components/preferences/atoms/root.js +1 -1
- package/dist/components/preferences/atoms/vendor-list.js +1 -1
- package/dist/components/preferences/index.js +1 -1
- package/dist/components/prompt/policy-actions.js +1 -1
- package/dist/components/shared/primitives/button.js +1 -1
- package/dist/components/shared/ui/accordion/accordion.js +1 -1
- package/dist/components/shared/ui/collapsible/collapsible.js +1 -1
- package/dist/components/shared/ui/logo.js +1 -1
- package/dist/components/shared/ui/preference-item/preference-item.js +1 -1
- package/dist/components/shared/ui/switch/switch.js +1 -1
- package/dist/components/shared/ui/tabs/tabs.js +1 -1
- package/dist/consent-theme.js +1 -0
- package/dist/draft.js +1 -1
- package/dist/external-iab-context.js +1 -1
- package/dist/headless.js +1 -1
- package/dist/hooks/use-committed-ref.js +2 -0
- package/dist/hooks.js +1 -1
- package/dist/iab/styles.tw3.css +1 -41
- package/dist/iab-context.js +1 -1
- package/dist/index.js +1 -1
- package/dist/kernel-selector.js +1 -0
- package/dist/module-hooks/iframe-blocker.js +1 -1
- package/dist/module-hooks/network-blocker.js +1 -1
- package/dist/module-hooks/network-hold.js +2 -0
- package/dist/module-hooks/persistence.js +1 -1
- package/dist/panel.js +1 -1
- package/dist/preferences.js +1 -2
- package/dist/provider.js +1 -1
- package/dist/styles.tw3.css +0 -45
- package/dist/ui-save.js +1 -1
- package/dist/utils/deferred-module.js +1 -1
- package/dist/version.js +1 -1
- package/dist-types/chunk-warming.d.ts +41 -8
- package/dist-types/component-hooks/use-headless-iab-consent-ui.d.ts +1 -1
- package/dist-types/components/iab-panel/atoms/purpose-item.d.ts +4 -1
- package/dist-types/components/iab-panel/atoms/stack-item.d.ts +6 -1
- package/dist-types/components/iab-panel/index.d.ts +1 -0
- package/dist-types/components/panel/index.d.ts +1 -0
- package/dist-types/components/panel/panel.d.ts +1 -1
- package/dist-types/components/panel-trigger/atoms/trigger-icons.d.ts +20 -0
- package/dist-types/components/preferences/index.d.ts +1 -0
- package/dist-types/components/shared/ui/accordion/accordion.d.ts +1 -0
- package/dist-types/components/shared/ui/collapsible/collapsible.d.ts +1 -0
- package/dist-types/components/shared/ui/logo.d.ts +0 -10
- package/dist-types/components/shared/ui/preference-item/preference-item.d.ts +14 -0
- package/dist-types/components/shared/ui/switch/switch.d.ts +1 -0
- package/dist-types/components/shared/ui/tabs/tabs.d.ts +1 -0
- package/dist-types/consent-theme.d.ts +38 -0
- package/dist-types/draft.d.ts +43 -6
- package/dist-types/headless.d.ts +0 -1
- package/dist-types/hooks/use-committed-ref.d.ts +24 -0
- package/dist-types/hooks.d.ts +2 -0
- package/dist-types/index.d.ts +3 -2
- package/dist-types/kernel-selector.d.ts +66 -0
- package/dist-types/module-hooks/iframe-blocker.d.ts +27 -0
- package/dist-types/module-hooks/network-blocker.d.ts +23 -0
- package/dist-types/module-hooks/network-hold.d.ts +47 -0
- package/dist-types/panel.d.ts +13 -1
- package/dist-types/preferences.d.ts +12 -1
- package/dist-types/provider.d.ts +41 -8
- package/dist-types/ui-config-context.d.ts +2 -0
- package/dist-types/ui-save.d.ts +21 -1
- package/dist-types/utils/deferred-module.d.ts +3 -1
- package/dist-types/version.d.ts +1 -1
- package/docs/README.md +6 -1
- package/docs/customization/overview.md +4 -3
- package/docs/customization/recipes.md +4 -2
- package/docs/customization/tokens.md +66 -3
- package/docs/frameworks/react/components/consent-dialog.md +45 -6
- package/docs/frameworks/react/components/consent-gate.md +8 -4
- package/docs/frameworks/react/components/consent-manager-provider.md +34 -14
- package/docs/frameworks/react/components/consent-widget.md +9 -0
- package/docs/frameworks/react/concepts/consent-categories.md +8 -1
- package/docs/frameworks/react/hooks/{use-consent-manager/overview.md → overview.md} +7 -5
- package/docs/frameworks/react/iab/overview.md +121 -1
- package/docs/frameworks/react/network-blocker.md +146 -0
- package/docs/frameworks/react/script-loader.md +6 -0
- package/docs/frameworks/react/styling/overview.md +71 -22
- package/docs/guides/consent-state.md +327 -0
- package/docs/guides/shared-consent-controls.md +158 -0
- package/docs/integrations/adobe-analytics.md +1 -1
- package/docs/integrations/ahrefs-analytics.md +1 -1
- package/docs/integrations/amplitude.md +1 -1
- package/docs/integrations/clearbit.md +1 -1
- package/docs/integrations/cloudflare-web-analytics.md +1 -1
- package/docs/integrations/cloudflare-zaraz.md +1 -1
- package/docs/integrations/crisp.md +1 -1
- package/docs/integrations/databuddy.md +1 -1
- package/docs/integrations/fathom-analytics.md +1 -1
- package/docs/integrations/front-chat.md +322 -0
- package/docs/integrations/google-maps.md +1 -1
- package/docs/integrations/google-tag-manager.md +1 -1
- package/docs/integrations/google-tag.md +1 -1
- package/docs/integrations/granular-consent.md +3 -1
- package/docs/integrations/heap.md +1 -1
- package/docs/integrations/hightouch.md +1 -1
- package/docs/integrations/hotjar.md +1 -1
- package/docs/integrations/intercom.md +1 -1
- package/docs/integrations/linkedin-insights.md +1 -1
- package/docs/integrations/logrocket.md +1 -1
- package/docs/integrations/matomo-analytics.md +1 -1
- package/docs/integrations/meta-pixel.md +1 -1
- package/docs/integrations/microsoft-clarity.md +1 -1
- package/docs/integrations/microsoft-uet.md +1 -1
- package/docs/integrations/mixpanel-analytics.md +1 -1
- package/docs/integrations/one-dollar-stats.md +305 -0
- package/docs/integrations/openai-pixel.md +1 -1
- package/docs/integrations/overview.md +17 -14
- package/docs/integrations/pinterest-tag.md +321 -0
- package/docs/integrations/pirsch.md +1 -1
- package/docs/integrations/plausible-analytics.md +1 -1
- package/docs/integrations/posthog.md +1 -1
- package/docs/integrations/promptwatch.md +1 -1
- package/docs/integrations/reddit-pixel.md +1 -1
- package/docs/integrations/rudderstack.md +1 -1
- package/docs/integrations/rybbit-analytics.md +1 -1
- package/docs/integrations/segment.md +1 -1
- package/docs/integrations/snapchat-pixel.md +1 -1
- package/docs/integrations/tiktok-pixel.md +1 -1
- package/docs/integrations/umami-analytics.md +1 -1
- package/docs/integrations/vercel-analytics.md +1 -1
- package/docs/integrations/x-pixel.md +1 -1
- package/docs/integrations/youtube.md +1 -1
- package/docs/upgrade-v3.md +129 -1
- package/package.json +7 -7
- package/dist/component-hooks/use-manager.js +0 -2
- package/dist-types/component-hooks/use-manager.d.ts +0 -45
|
@@ -11,10 +11,53 @@ React, Next.js and Svelte provide `styles.css`. Import the adapter's stylesheet
|
|
|
11
11
|
at the app's global entry point. Vue includes styles in its components; Astro
|
|
12
12
|
adds styles through its integration.
|
|
13
13
|
|
|
14
|
+
The stylesheet blocks rendering, so it carries only what a first paint can
|
|
15
|
+
show: the default tokens, every c15t CSS variable, and the rules for the
|
|
16
|
+
banner, `ConsentDialogTrigger` and the `ConsentGate` placeholder. The consent
|
|
17
|
+
dialog and preference widget bring their own rules. Each rule reaches the page
|
|
18
|
+
once.
|
|
19
|
+
|
|
14
20
|
```tsx
|
|
15
21
|
import 'c15t/react/styles.css';
|
|
16
22
|
```
|
|
17
23
|
|
|
24
|
+
| File | Holds | Loaded by |
|
|
25
|
+
| -------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------- |
|
|
26
|
+
| `styles.css` or `styles.tw3.css` | Default tokens, all variables, banner, trigger and `ConsentGate` rules | Your app, once |
|
|
27
|
+
| `@c15t/ui/styles/dialog.css` | Dialog and preference widget rules | The dialog component, with its lazy chunk |
|
|
28
|
+
| `@c15t/ui/styles/primitives.css` | Rules for the `@c15t/ui/styles/primitives` class maps | Svelte's `styles.css`, or your app if it renders those class maps |
|
|
29
|
+
| `iab/styles.css` | IAB TCF banner and dialog rules and variables | Your app, after `styles.css` |
|
|
30
|
+
|
|
31
|
+
In React, Next.js and TanStack Start, the dialog's module imports
|
|
32
|
+
`@c15t/ui/styles/dialog.css`. The bundler emits it with the dialog's chunk and
|
|
33
|
+
loads it before that chunk runs, so the dialog never renders unstyled. The
|
|
34
|
+
chunk loads after first paint, by the time the dialog first opens. Do not
|
|
35
|
+
import `dialog.css` yourself. The `@c15t/react/components/consent-dialog`,
|
|
36
|
+
`/components/consent-widget`, `/primitives`, `/primitives/*` and `/iab` entry
|
|
37
|
+
points import it as soon as you import them, because they render dialog parts
|
|
38
|
+
outside the lazy chunk.
|
|
39
|
+
|
|
40
|
+
These modules import the stylesheet through `@c15t/ui/styles/dialog`. Under
|
|
41
|
+
the `node` export condition that module imports nothing, so server code that
|
|
42
|
+
loads `@c15t/react` with plain Node, such as the Pages Router or an SSR build
|
|
43
|
+
that keeps dependencies external, does not fail on the `.css` file. In your
|
|
44
|
+
own components, import `@c15t/ui/styles/dialog` rather than the `.css` file
|
|
45
|
+
if the module can run on the server.
|
|
46
|
+
|
|
47
|
+
Svelte loads its dialog with the page, so `c15t/svelte/styles.css` also
|
|
48
|
+
imports the dialog and primitive rules. Astro injects the banner rules; the
|
|
49
|
+
React and Svelte dialog islands import the rest, and Astro links those
|
|
50
|
+
stylesheets on every page, so in Astro the dialog rules still block rendering.
|
|
51
|
+
Vue components import their own stylesheets.
|
|
52
|
+
|
|
53
|
+
The dialog rules sit in `@layer components` and declare no variables, so
|
|
54
|
+
tokens from `theme`, variables you override in your own CSS, and Tailwind 4
|
|
55
|
+
utilities still win over them even though they load later. If you import
|
|
56
|
+
`styles.css` into a named layer, such as `@import 'c15t/react/styles.css'
|
|
57
|
+
layer(c15t)`, the dialog rules still join the top-level `components` layer.
|
|
58
|
+
List `components` in your layer order statement, for example
|
|
59
|
+
`@layer c15t, components, app;`, so it does not land after your own layers.
|
|
60
|
+
|
|
18
61
|
The standard stylesheet places rules in `@layer components`. For Tailwind 3,
|
|
19
62
|
use the `styles.tw3.css` entry instead of the standard stylesheet, between the
|
|
20
63
|
components and utilities directives in your Tailwind entry:
|
|
@@ -26,6 +69,18 @@ components and utilities directives in your Tailwind entry:
|
|
|
26
69
|
@tailwind utilities;
|
|
27
70
|
```
|
|
28
71
|
|
|
72
|
+
Tailwind 3 also processes the dialog stylesheet the bundler loads, and it
|
|
73
|
+
rejects a stylesheet that uses `@layer components` without its own
|
|
74
|
+
`@tailwind components` directive. Add the c15t plugin before `tailwindcss` in
|
|
75
|
+
your PostCSS config. It removes the layer wrapper from c15t's stylesheets so
|
|
76
|
+
Tailwind 3 accepts them and its preflight does not override them:
|
|
77
|
+
|
|
78
|
+
```js title="postcss.config.mjs"
|
|
79
|
+
export default {
|
|
80
|
+
plugins: ['@c15t/ui/postcss-tailwind3', 'tailwindcss', 'autoprefixer'],
|
|
81
|
+
};
|
|
82
|
+
```
|
|
83
|
+
|
|
29
84
|
Do not load both c15t stylesheet variants. For Tailwind 4 or unlayered CSS,
|
|
30
85
|
inspect layer order before reaching for `!important`.
|
|
31
86
|
|
|
@@ -48,9 +103,17 @@ export const theme = defineTheme({
|
|
|
48
103
|
});
|
|
49
104
|
```
|
|
50
105
|
|
|
51
|
-
Install `@c15t/ui` if importing its theme helper directly.
|
|
52
|
-
|
|
53
|
-
|
|
106
|
+
Install `@c15t/ui` if importing its theme helper directly. The browser does
|
|
107
|
+
not turn tokens into CSS. In React and Next.js, render
|
|
108
|
+
`<ConsentTheme theme={theme} />` where the app renders on the server, and pass
|
|
109
|
+
`theme` in your provider options for `consentActions`. Elsewhere, call
|
|
110
|
+
`generateThemeCSS(theme)` from `@c15t/ui/theme` on the server or at build time
|
|
111
|
+
and put the result in a `<style>` element or your stylesheet. See
|
|
112
|
+
[React styling](../frameworks/react/styling/overview.md) and
|
|
113
|
+
[Next.js styling](https://c15t.com/docs/frameworks/next/styling/overview).
|
|
114
|
+
|
|
115
|
+
`consentActions` selects styling by action role. A per-action entry overrides
|
|
116
|
+
`primary`, which overrides `default`.
|
|
54
117
|
|
|
55
118
|
## Target a prompt with CSS
|
|
56
119
|
|
|
@@ -97,14 +97,44 @@ it is `dialog`. These set that surface:
|
|
|
97
97
|
* `useSetActiveUI()('dialog')` in your own component, or `openDialog()`
|
|
98
98
|
from `useHeadlessConsentUI()` on the headless subpath.
|
|
99
99
|
|
|
100
|
-
The component code is split into its own chunk
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
The component code is split into its own chunk, so it is not part of the
|
|
101
|
+
first page load. It renders nothing until that chunk is ready, and the
|
|
102
|
+
chunk downloads at the first of these:
|
|
103
|
+
|
|
104
|
+
* The browser's first idle period after the page's `load` event, while the
|
|
105
|
+
banner is shown or a `ConsentDialogLink`, `ConsentDialogTrigger`,
|
|
106
|
+
`ConsentGate` button or other button that opens the dialog is mounted.
|
|
107
|
+
This covers a visitor who taps or presses Enter with no hover or focus
|
|
108
|
+
first. Browsers without `requestIdleCallback`, such as Safari, wait
|
|
109
|
+
200 ms after `load` instead.
|
|
110
|
+
* Hover or focus on a button that opens the dialog.
|
|
111
|
+
* The dialog opening.
|
|
112
|
+
|
|
113
|
+
Passing `open={true}` or `showTrigger` loads it on mount. A visit with
|
|
114
|
+
saved consent and nothing on the page that opens the dialog never
|
|
115
|
+
downloads it.
|
|
116
|
+
|
|
117
|
+
Idle loading is skipped when the visitor has Save-Data on
|
|
118
|
+
(`navigator.connection.saveData`), on a `2g` or `slow-2g` connection, and
|
|
119
|
+
while offline; hover and focus still load it. To load the chunk only on
|
|
120
|
+
hover, focus or open, set `preloadDialog: 'intent'` in the provider
|
|
121
|
+
options. The default is `'idle'`. With Next.js, pass it through
|
|
122
|
+
`ConsentRoot`'s `options` prop.
|
|
123
|
+
|
|
124
|
+
If a preload fails, for example because the visitor is offline, nothing is
|
|
125
|
+
cached: the next hover, focus or open tries the download again.
|
|
126
|
+
|
|
127
|
+
The dialog's CSS travels in the same chunk and is applied before the dialog
|
|
128
|
+
renders, so your app's `styles.css` import stays small.
|
|
104
129
|
|
|
105
130
|
Without an `open` prop, Escape closes the dialog. Save, Accept all and
|
|
106
|
-
Reject all close it
|
|
107
|
-
|
|
131
|
+
Reject all close it as soon as the choice is recorded in the browser, in
|
|
132
|
+
the same task as the click; the exit animation still plays. The request to
|
|
133
|
+
the backend runs afterwards and never reopens the dialog: a failed request
|
|
134
|
+
keeps the choice, reports through `onError` and is retried later. See
|
|
135
|
+
[when a choice is saved](../../../guides/consent-state.md#when-a-choice-is-saved)
|
|
136
|
+
for the order of events. A draft that went stale because the policy changed keeps the dialog open
|
|
137
|
+
for review. With `open={true}`, these actions leave the dialog visible; the
|
|
108
138
|
parent must set `open={false}` to close it. Clicking the backdrop does not
|
|
109
139
|
close it. Closing discards toggles that were not saved: the next open starts
|
|
110
140
|
from the recorded choice again. After a save from an uncontrolled dialog,
|
|
@@ -186,6 +216,15 @@ for the stock structure are `dialog.root`, `dialog.container`,
|
|
|
186
216
|
`dialog.card`, `dialog.header`, `dialog.title`, `dialog.content`,
|
|
187
217
|
`dialog.overlay`, `description.dialog`, `manager.footer` and `tag.dialog`.
|
|
188
218
|
|
|
219
|
+
`ConsentDialog` from the package root and from the
|
|
220
|
+
`c15t/react/consent-dialog` subpath is the deferred component described
|
|
221
|
+
under Behavior, and each `ConsentDialog.<Part>` loads with the same chunk.
|
|
222
|
+
`c15t/react/components/consent-dialog` exports the dialog without the
|
|
223
|
+
deferral, plus each part as a named export (`Card`, `Header`, `Overlay`,
|
|
224
|
+
`Root` and the rest). Importing from that path puts the dialog's code in
|
|
225
|
+
the bundle of every page that imports it, so the first open needs no
|
|
226
|
+
download but every visitor downloads the dialog.
|
|
227
|
+
|
|
189
228
|
The compound parts import from `c15t/react` as properties of `ConsentDialog`,
|
|
190
229
|
and `ConsentWidget` from the same module. See
|
|
191
230
|
[Styling](../styling/overview.md) for slots and tokens.
|
|
@@ -84,9 +84,11 @@ scope cannot be granted. The built-in placeholder then shows `frame.policyBlocke
|
|
|
84
84
|
the button, and a stored grant for that category stays blocked.
|
|
85
85
|
|
|
86
86
|
When the provider starts from a snapshot prefetched on the server, the
|
|
87
|
-
server renders the
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
server renders the placeholder for a denied category. For a granted
|
|
88
|
+
category it renders the empty wrapper, and the children mount once
|
|
89
|
+
hydration completes, so an iframe loads once even when the page streams
|
|
90
|
+
inside a `Suspense` boundary. A grant in the request cookie allows the
|
|
91
|
+
embed only when policy and privacy signals permit it. Browser privacy signals detected during hydration can
|
|
90
92
|
withdraw that permission. In a browser-only setup the
|
|
91
93
|
provider has no permission until it resolves policy on the client, so
|
|
92
94
|
`ConsentGate` shows the placeholder first and swaps in the embed after resolution
|
|
@@ -142,7 +144,9 @@ center and is not restricted by policy or privacy signals such as GPC.
|
|
|
142
144
|
Load the page with the category denied. The placeholder text names the
|
|
143
145
|
category and the network panel shows no request to the embed's host. With a
|
|
144
146
|
prefetched snapshot, the server HTML contains the placeholder for a denied
|
|
145
|
-
category
|
|
147
|
+
category and neither placeholder nor embed for a granted category; the
|
|
148
|
+
embed appears after hydration and the network panel shows one request for
|
|
149
|
+
its document. Browser-only initialization
|
|
146
150
|
shows the placeholder before policy resolves, then replaces it with the
|
|
147
151
|
embed if the category is granted. After policy resolves, activate the
|
|
148
152
|
placeholder button: the preference center opens. Turn the category on and
|
|
@@ -35,16 +35,18 @@ subscriptions. Keep it mounted across client navigation. Its initial mode and
|
|
|
35
35
|
prefetch configuration belong to the runtime's creation, not a per-render
|
|
36
36
|
mechanism for switching visitors or backends.
|
|
37
37
|
|
|
38
|
-
| Option
|
|
39
|
-
|
|
|
40
|
-
| `mode`
|
|
41
|
-
| `scripts`
|
|
42
|
-
| `callbacks`
|
|
43
|
-
| `theme`, `components`
|
|
44
|
-
| `presentation`
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
38
|
+
| Option | Purpose |
|
|
39
|
+
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
40
|
+
| `mode` | Hosted, offline or custom transport factory |
|
|
41
|
+
| `scripts` | Consent-managed script configurations |
|
|
42
|
+
| `callbacks` | Choice, permission and error events |
|
|
43
|
+
| `theme`, `components` | Consent-action styles, theme slots and component slot attributes. Tokens render through `ConsentTheme` |
|
|
44
|
+
| `presentation` | Prompt and preferences layout behavior |
|
|
45
|
+
| `preloadDialog` | When the deferred `ConsentDialog` starts loading: `'idle'` (default) or `'intent'`; see [ConsentDialog](./consent-dialog.md#behavior) |
|
|
46
|
+
| `i18n` | Languages and message overrides |
|
|
47
|
+
| `storageConfig` | Browser record storage configuration |
|
|
48
|
+
| `enabled` | Set false only when intentionally bypassing consent enforcement |
|
|
49
|
+
| `reloadOnConsentRevoked` | Reload the page after the visitor turns off a granted category or vendor; defaults to `true` |
|
|
48
50
|
|
|
49
51
|
`enabled: false` grants categories and allows gated loading while suppressing
|
|
50
52
|
consent UI. It is not a way to fix failed initialization or hide a banner in
|
|
@@ -58,13 +60,31 @@ signals. Hydration and notice dismissal do not become explicit choices.
|
|
|
58
60
|
|
|
59
61
|
An externally owned runtime can be passed through `runtime`. Its owner must
|
|
60
62
|
start and dispose it. A provider borrowing that runtime must not initialize or
|
|
61
|
-
dispose a second copy.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
dispose a second copy. Passing a different `runtime` moves the provider and its
|
|
64
|
+
hooks to the new runtime's kernel and unsubscribes them from the previous one.
|
|
65
|
+
The previous runtime keeps running until its owner disposes it. Switch the
|
|
66
|
+
provider to another runtime, or unmount it, before disposing the runtime it
|
|
67
|
+
renders. Changing between a borrowed runtime and a provider-created one still
|
|
68
|
+
requires remounting the provider. IAB actions taken before its handle is ready
|
|
69
|
+
wait for that handle. If the provider unmounts or switches runtimes first, a
|
|
70
|
+
pending `save()` rejects with an `AbortError` instead of waiting indefinitely.
|
|
65
71
|
See [consent state](../../../guides/consent-state.md) and
|
|
66
72
|
[deployment modes](../../../guides/deployment-modes.md).
|
|
67
73
|
|
|
74
|
+
## Reload after revocation
|
|
75
|
+
|
|
76
|
+
Removing a script cannot stop code that already ran. A vendor's listeners,
|
|
77
|
+
timers and widgets keep working until the page unloads. When an accept, reject
|
|
78
|
+
or save turns off a category or vendor that was granted, the provider reloads
|
|
79
|
+
the page once the save request settles, so the next page runs only permitted
|
|
80
|
+
code. `onBeforeConsentRevocationReload` runs just before the reload. With a
|
|
81
|
+
[`consentSource`](../../../guides/shared-consent-controls.md), any withdrawal the
|
|
82
|
+
external CMP reports reloads the same way, including expiry.
|
|
83
|
+
|
|
84
|
+
Otherwise, expiry, policy changes and privacy signals do not reload the page. Set
|
|
85
|
+
`reloadOnConsentRevoked: false` only when every gated vendor stops itself on
|
|
86
|
+
revocation, for example through its own opt-out API.
|
|
87
|
+
|
|
68
88
|
## Clear data when consent is denied
|
|
69
89
|
|
|
70
90
|
Set `options.clearOnRevocation` to declare cookies, localStorage keys, and
|
|
@@ -93,6 +93,9 @@ styled like a category row: the vendor's name and a switch on the header,
|
|
|
93
93
|
and its description and privacy policy link behind the card's own expand
|
|
94
94
|
control. Cards start collapsed, so a long list stays one line per vendor,
|
|
95
95
|
and the category list scrolls inside the dialog rather than growing it.
|
|
96
|
+
A collapsed category or card renders its content element empty and mounts
|
|
97
|
+
the content the first time it opens, then keeps it mounted, so a long
|
|
98
|
+
vendor list costs nothing until a visitor expands its category.
|
|
96
99
|
Vendor switches edit the same draft as the category
|
|
97
100
|
switches and record on Save. Saving with one vendor off denies that vendor
|
|
98
101
|
only; the category stays granted and its other vendors keep loading. While
|
|
@@ -191,6 +194,12 @@ Provider component slots for the stock structure are `manager.root`,
|
|
|
191
194
|
`vendor-list.name`, `vendor-list.description`, `vendor-list.link`,
|
|
192
195
|
`vendor-list.control` and `tag.manager`.
|
|
193
196
|
|
|
197
|
+
`ConsentWidget` from the package root and from the
|
|
198
|
+
`c15t/react/consent-widget` subpath loads its code in a separate chunk the
|
|
199
|
+
first time it renders, so a page that never renders it does not download
|
|
200
|
+
it. `c15t/react/components/consent-widget` exports the widget without that
|
|
201
|
+
deferral, plus each part as a named export.
|
|
202
|
+
|
|
194
203
|
The compound parts import from `c15t/react` as properties of `ConsentWidget`.
|
|
195
204
|
See [Styling](../styling/overview.md) for slots and tokens.
|
|
196
205
|
|
|
@@ -53,7 +53,14 @@ Analytics.
|
|
|
53
53
|
Hidden optional categories do not need a choice to dismiss the banner. Their
|
|
54
54
|
permissions still follow the backend policy, so hidden opt-in categories remain
|
|
55
55
|
denied without a valid grant. React providers enable DOM iframe blocking and
|
|
56
|
-
discovery by default
|
|
56
|
+
discovery by default. The blocker loads when the first iframe with
|
|
57
|
+
`data-category` or `data-vendor` is on the page, so pages without one never
|
|
58
|
+
download it; until it runs, such an iframe that arrives with a `src` consent
|
|
59
|
+
does not allow is paused. Write gated iframes with `data-src`, not `src`. The
|
|
60
|
+
browser requests a `src` as soon as the iframe is in the document, before the
|
|
61
|
+
blocker can pause it, whether the iframe comes from the server HTML or a
|
|
62
|
+
client render. Set
|
|
63
|
+
`iframeBlocker: false` to disable it, or
|
|
57
64
|
`iframeBlocker: { disableAutomaticBlocking: true }` to scan manually with
|
|
58
65
|
`useIframeBlocker({ disableAutomaticBlocking: true }).processAllIframes()`.
|
|
59
66
|
|
|
@@ -51,8 +51,10 @@ This is an isolated action example, not a complete policy-aware banner. Use the
|
|
|
51
51
|
stock UI or headless surface actions for a complete prompt. Do not call save on
|
|
52
52
|
mount to persist a state derived from permissions.
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
of
|
|
54
|
+
Use focused hooks for feature gates. For a complete custom prompt or preference
|
|
55
|
+
center, use `useHeadlessConsentUI()` for surface actions and `useConsentDraft()`
|
|
56
|
+
for staged choices, inside one `ConsentDraftProvider` so the save commits the
|
|
57
|
+
same draft. v3 has no `useConsentManager()`; the
|
|
58
|
+
[upgrade guide](../../../upgrade-v3.md#replace-useconsentmanager) maps each of its
|
|
59
|
+
fields to a hook. Read [consent state](../../../guides/consent-state.md) before
|
|
60
|
+
treating a callback as evidence of a choice.
|
|
@@ -16,7 +16,9 @@ components inside an existing consent provider connected to Inth.
|
|
|
16
16
|
|
|
17
17
|
Import the components from `c15t/react/iab` and render them inside your
|
|
18
18
|
existing `ConsentProvider` from `c15t/react`. Import `c15t/react/iab/styles.css`
|
|
19
|
-
at your global stylesheet entry.
|
|
19
|
+
at your global stylesheet entry, after `c15t/react/styles.css`: the IAB
|
|
20
|
+
stylesheet holds only the IAB rules and uses the tokens from the base
|
|
21
|
+
stylesheet.
|
|
20
22
|
|
|
21
23
|
```tsx title="src/iab-consent.tsx"
|
|
22
24
|
import { IABProvider, IABConsentBanner, IABConsentDialog } from 'c15t/react/iab';
|
|
@@ -51,3 +53,121 @@ Test category choices alongside vendor, purpose, legitimate-interest and special
|
|
|
51
53
|
feature controls used by your integration. Confirm the TC String and `__tcfapi`
|
|
52
54
|
reflect the saved state, and verify the vendor's actual requests. A category-only
|
|
53
55
|
banner test does not establish that the IAB flow works.
|
|
56
|
+
|
|
57
|
+
## Restrict how vendors use a purpose
|
|
58
|
+
|
|
59
|
+
Publisher restrictions override a vendor's declared use of one purpose. Pass
|
|
60
|
+
them to `IABProvider` as `publisherRestrictions`. The vendor IDs below are
|
|
61
|
+
placeholders; use vendors from your list whose declarations allow each
|
|
62
|
+
restriction.
|
|
63
|
+
|
|
64
|
+
```tsx title="src/iab-consent.tsx"
|
|
65
|
+
import { IABProvider, IABConsentBanner, IABConsentDialog } from 'c15t/react/iab';
|
|
66
|
+
|
|
67
|
+
const publisherRestrictions = [
|
|
68
|
+
// Vendors 10 and 755 may not use purpose 4.
|
|
69
|
+
{ purposeId: 4, restrictionType: 0 as const, vendorIds: [10, 755] },
|
|
70
|
+
// Vendor 755 must use legitimate interest for purpose 7.
|
|
71
|
+
{ purposeId: 7, restrictionType: 2 as const, vendorIds: [755] },
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
export function IABConsent({ cmpId }: { cmpId: number }) {
|
|
75
|
+
return (
|
|
76
|
+
<IABProvider cmpId={cmpId} publisherRestrictions={publisherRestrictions}>
|
|
77
|
+
<IABConsentBanner />
|
|
78
|
+
<IABConsentDialog />
|
|
79
|
+
</IABProvider>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Each restriction type has a requirement that depends on the vendor list:
|
|
85
|
+
|
|
86
|
+
| `restrictionType` | Meaning | Allowed when the vendor |
|
|
87
|
+
| ----------------- | -------------------------------------- | ------------------------------------------------------------------------------- |
|
|
88
|
+
| `0` | Purpose not allowed on any legal basis | declares the purpose for consent or legitimate interest |
|
|
89
|
+
| `1` | Consent required | declares the purpose for legitimate interest and lists it in `flexiblePurposes` |
|
|
90
|
+
| `2` | Legitimate interest required | declares the purpose for consent and lists it in `flexiblePurposes` |
|
|
91
|
+
|
|
92
|
+
Type `2` is never allowed for purposes 1, 3, 4, 5 and 6, which TCF allows
|
|
93
|
+
only with consent. Restrictions also require a service-specific CMP, the
|
|
94
|
+
default. Setting `isServiceSpecific: false` with restrictions is an error.
|
|
95
|
+
c15t copies the restrictions when the CMP starts, so changing your array
|
|
96
|
+
afterwards has no effect.
|
|
97
|
+
|
|
98
|
+
c15t checks restrictions when the vendor list loads and again before each TC
|
|
99
|
+
String is encoded. Unsupported restrictions include type `3`, which the spec
|
|
100
|
+
reserves, a vendor or purpose missing from the list, and a vendor with two
|
|
101
|
+
restriction types for one purpose. They are never dropped. Loading IAB
|
|
102
|
+
settings, saving and generating a TC String all reject with a
|
|
103
|
+
`PublisherRestrictionError` exported by `@c15t/iab`. On a `createIAB` handle
|
|
104
|
+
these are `whenReady()`, `save()` and `generateTCString()`. No TC String is
|
|
105
|
+
written and no consent is recorded. Retrying `whenReady()` does not fetch
|
|
106
|
+
another vendor list. The error lasts until a new vendor list is set up: with an
|
|
107
|
+
explicit `gvl` option that never happens, so saving keeps failing even if the
|
|
108
|
+
kernel later holds a different list. When the CMP follows the kernel's list
|
|
109
|
+
instead, a replacement list is checked again and saving works once it accepts
|
|
110
|
+
the restrictions. When a replacement vendor list makes a restriction
|
|
111
|
+
unsupported, c15t also clears the TC authority confirmed under the previous
|
|
112
|
+
list, so gated scripts stop until the configuration is fixed. Whenever c15t
|
|
113
|
+
withdraws the authority it confirmed, it also removes the TC String from the
|
|
114
|
+
`euconsent-v2` cookie and localStorage entry. Because the check reads the
|
|
115
|
+
current vendor list, a vendor that stops declaring a purpose or its flexibility
|
|
116
|
+
makes a restriction invalid. Narrow a `vendors` allowlist and your restrictions
|
|
117
|
+
together.
|
|
118
|
+
|
|
119
|
+
Saved TC Strings carry the restrictions in their `PubRestrictions` section, and
|
|
120
|
+
`__tcfapi('getTCData')` reports them as `publisher.restrictions`, keyed by
|
|
121
|
+
purpose ID, then vendor ID. A stored TC String whose restrictions differ from
|
|
122
|
+
the current configuration is not restored. If the visitor's stored choice is
|
|
123
|
+
otherwise current, the banner opens again, as it does after a policy change,
|
|
124
|
+
and closes once the visitor saves. IAB gates that depend on the TC String stay
|
|
125
|
+
denied until then. c15t also removes the superseded TC String from the
|
|
126
|
+
`euconsent-v2` cookie and localStorage entry, so vendors reading standard
|
|
127
|
+
storage do not pick it up. An unchanged configuration restores the stored TC
|
|
128
|
+
String without asking.
|
|
129
|
+
When reading a string written under TCF policy version 2 or 3, c15t accepts
|
|
130
|
+
type `2` for purposes 3 to 6, which those versions allowed. Strings c15t
|
|
131
|
+
writes always follow the current policy.
|
|
132
|
+
|
|
133
|
+
The React, Vue, Svelte and `@c15t/browser/iab` preference centres list each
|
|
134
|
+
vendor under the legal basis the restrictions leave it. A vendor whose purpose
|
|
135
|
+
now requires legitimate interest gets an objection control instead of a
|
|
136
|
+
consent toggle, and a prohibited purpose no longer lists the vendor. A
|
|
137
|
+
purpose whose vendors all use legitimate interest has no consent switch,
|
|
138
|
+
because turning one off would change nothing the vendors rely on; its
|
|
139
|
+
objection control is the opt-out. Such a purpose does not decide its c15t
|
|
140
|
+
category: the category follows the purposes in it that the visitor can
|
|
141
|
+
consent to. Legitimate interest never grants a category on its own. If none of
|
|
142
|
+
a category's purposes has a consent basis, the category needs all of them
|
|
143
|
+
consented, which only Accept all does. Stack switches
|
|
144
|
+
cover only the purposes some vendor processes on consent. Display-model rows
|
|
145
|
+
expose this as `hasConsentBasis`. Custom preference UIs can call
|
|
146
|
+
`applyPublisherRestrictionsToGVL` from `@c15t/iab/headless`, or pass
|
|
147
|
+
`publisherRestrictions` to `processGVLForDialog`, to get the same vendor
|
|
148
|
+
declarations. The configured restrictions are on the kernel's IAB state as
|
|
149
|
+
`publisherRestrictions`.
|
|
150
|
+
|
|
151
|
+
Consent-gated scripts, network rules and iframes with a `vendorId` also apply
|
|
152
|
+
the confirmed restrictions for that vendor:
|
|
153
|
+
|
|
154
|
+
* Type `0` blocks a target that declares the purpose in `iabPurposes` or
|
|
155
|
+
`iabLegIntPurposes`.
|
|
156
|
+
* Type `1` makes a purpose declared in `iabLegIntPurposes` require purpose and
|
|
157
|
+
vendor consent. Legitimate interest no longer satisfies it.
|
|
158
|
+
* Type `2` makes a purpose declared in `iabPurposes` require purpose and vendor
|
|
159
|
+
legitimate interest. Consent no longer satisfies it.
|
|
160
|
+
|
|
161
|
+
Types `1` and `2` block the target when the vendor list does not mark the
|
|
162
|
+
purpose as flexible for that vendor. Targets without a `vendorId` ignore
|
|
163
|
+
restrictions. A target that uses only legitimate interest once restrictions
|
|
164
|
+
apply is not blocked by a refused category, since it needs no consent; its
|
|
165
|
+
legitimate interest signals and the visitor's objection decide. GPC, opt-out
|
|
166
|
+
directives and strict scope still block it, and the category stays refused for
|
|
167
|
+
scripts that depend on it. Accept all grants the vendor signal a restriction
|
|
168
|
+
moves a purpose to, including legitimate interest for a vendor that declares
|
|
169
|
+
none. Legitimate interest a restriction introduces applies until the visitor
|
|
170
|
+
objects: the preference centres show it as allowed, and saving without touching
|
|
171
|
+
it encodes it as allowed. `introducedLegitimateInterest` from
|
|
172
|
+
`@c15t/iab/headless` lists those purposes and vendors for custom preference
|
|
173
|
+
UIs.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Block network requests
|
|
3
|
+
description: Block fetch and XHR calls to third-party domains in a React app
|
|
4
|
+
until the visitor grants their consent category.
|
|
5
|
+
group: frameworks
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Add rules to the provider
|
|
9
|
+
|
|
10
|
+
The network blocker stops `fetch` and `XMLHttpRequest` calls to domains you
|
|
11
|
+
list until the visitor grants their category. Use it for beacons and API
|
|
12
|
+
calls that bypass script loading, such as a pixel fired by an SDK that is
|
|
13
|
+
already on the page.
|
|
14
|
+
|
|
15
|
+
Add `networkBlocker` to the `ConsentProvider` options from your
|
|
16
|
+
[quickstart](./quickstart.md). Set the URL to the endpoint
|
|
17
|
+
supplied by your Inth project.
|
|
18
|
+
|
|
19
|
+
```tsx title="components/consent.tsx"
|
|
20
|
+
'use client';
|
|
21
|
+
|
|
22
|
+
import type { ReactNode } from 'react';
|
|
23
|
+
import { ConsentBanner, ConsentDialog, ConsentProvider, hosted } from 'c15t/react';
|
|
24
|
+
|
|
25
|
+
const mode = hosted({ url: 'https://your-project.inth.app' });
|
|
26
|
+
const networkBlocker = {
|
|
27
|
+
rules: [
|
|
28
|
+
{ id: 'google-analytics', domain: 'google-analytics.com', category: 'measurement' },
|
|
29
|
+
{ id: 'meta-pixel', domain: 'facebook.com', pathIncludes: '/tr', category: 'marketing' },
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function Consent({ children }: { children: ReactNode }) {
|
|
34
|
+
return (
|
|
35
|
+
<ConsentProvider options={{ mode, networkBlocker }}>
|
|
36
|
+
{children}
|
|
37
|
+
<ConsentBanner />
|
|
38
|
+
<ConsentDialog />
|
|
39
|
+
</ConsentProvider>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Render your app inside `ConsentProvider`. Blocking starts when it renders, so
|
|
45
|
+
components outside it that send requests while rendering, or modules evaluated
|
|
46
|
+
before it, are not covered. See the limits below.
|
|
47
|
+
|
|
48
|
+
## Match requests with rules
|
|
49
|
+
|
|
50
|
+
Each rule names a `domain` and the consent `category` a request needs. The
|
|
51
|
+
domain also matches its subdomains: `google-analytics.com` covers
|
|
52
|
+
`www.google-analytics.com`. `pathIncludes` narrows the rule to paths that
|
|
53
|
+
contain a substring, and `methods` narrows it to HTTP methods. A request is
|
|
54
|
+
blocked when a matching rule's condition is not met by the visitor's
|
|
55
|
+
effective permissions.
|
|
56
|
+
|
|
57
|
+
`category` takes the same conditions as scripts:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
{ category: 'measurement' }
|
|
61
|
+
{ category: { and: ['measurement', 'marketing'] } }
|
|
62
|
+
{ category: { or: ['measurement', 'marketing'] } }
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Add `vendor` to also block the request while the visitor has turned that
|
|
66
|
+
vendor off. IAB TCF rules use `vendorId` and the `iab*` purpose fields
|
|
67
|
+
instead.
|
|
68
|
+
|
|
69
|
+
| Option | Default | Purpose |
|
|
70
|
+
| -------------------- | -------- | ------------------------------------------------------------ |
|
|
71
|
+
| `rules` | required | Rules described above |
|
|
72
|
+
| `enabled` | `true` | Set `false` to keep the rules but stop blocking |
|
|
73
|
+
| `logBlockedRequests` | `true` | Log each blocked request with `console.warn` |
|
|
74
|
+
| `onRequestBlocked` | none | Called with `{ method, url, rule }` for each blocked request |
|
|
75
|
+
|
|
76
|
+
## What a blocked request looks like
|
|
77
|
+
|
|
78
|
+
The blocker wraps `window.fetch` and `XMLHttpRequest`. A blocked `fetch`
|
|
79
|
+
resolves to a `451` response with the status text
|
|
80
|
+
`Request blocked by consent`, and nothing is sent. A blocked XHR is aborted
|
|
81
|
+
and fires an `error` event. Requests that match no rule are not delayed.
|
|
82
|
+
|
|
83
|
+
## When blocking starts
|
|
84
|
+
|
|
85
|
+
The provider holds matching requests from its first render in the browser,
|
|
86
|
+
before any of its children render or run effects. That covers requests from
|
|
87
|
+
child components, including their mount effects, and from effects in
|
|
88
|
+
components rendered next to the provider. The blocker module itself loads
|
|
89
|
+
after mount and decides each held request. Apps without `networkBlocker` do
|
|
90
|
+
not download it.
|
|
91
|
+
|
|
92
|
+
The standalone `useNetworkBlocker` hook works the same way from the first
|
|
93
|
+
render of the component that calls it. That render patches `fetch` and
|
|
94
|
+
`XMLHttpRequest`. If React throws the render away and never commits it, the
|
|
95
|
+
hold ends after 10 seconds. Nothing checked consent for the requests it held,
|
|
96
|
+
so they fail the way the blocker fails a blocked request: a 451 response for
|
|
97
|
+
`fetch`, a failed XHR. The same happens when the component unmounts before
|
|
98
|
+
the blocker loads.
|
|
99
|
+
|
|
100
|
+
While consent is unknown, a matching request that would be blocked waits
|
|
101
|
+
instead of failing. Consent is unknown until the policy has loaded, which is
|
|
102
|
+
also when a returning visitor's stored choice takes effect. The request is
|
|
103
|
+
then sent if the choice allows it and blocked otherwise. If the policy fails
|
|
104
|
+
to load, optional categories stay denied and waiting requests are blocked.
|
|
105
|
+
If the policy request never finishes, they keep waiting and are never sent.
|
|
106
|
+
|
|
107
|
+
A synchronous XHR cannot wait. Before the blocker module has loaded, a
|
|
108
|
+
matching one throws a `NetworkError` from `send()`. After that, one that
|
|
109
|
+
consent does not allow yet is blocked.
|
|
110
|
+
|
|
111
|
+
## What the network blocker cannot stop
|
|
112
|
+
|
|
113
|
+
The blocker only sees requests made after the provider starts rendering in
|
|
114
|
+
the browser, through `fetch` or `XMLHttpRequest`. It cannot stop:
|
|
115
|
+
|
|
116
|
+
* Code that runs before the provider renders: inline scripts in the HTML,
|
|
117
|
+
third-party tags in `<head>`, scripts loaded before hydration (such as
|
|
118
|
+
`next/script` with `beforeInteractive`), and client modules that evaluate
|
|
119
|
+
earlier. Webpack builds evaluate a route's client component modules when
|
|
120
|
+
its chunk loads, so their top-level code runs first. Turbopack evaluates a
|
|
121
|
+
client component module when its first element renders, which inside the
|
|
122
|
+
provider is after blocking starts.
|
|
123
|
+
* Code that saved its own reference to `fetch` or `XMLHttpRequest` before the
|
|
124
|
+
provider rendered.
|
|
125
|
+
* `navigator.sendBeacon`, `WebSocket`, `EventSource`, and requests made by
|
|
126
|
+
`<img>`, `<script>` or `<iframe>` elements, web workers and service
|
|
127
|
+
workers.
|
|
128
|
+
* With the standalone `useNetworkBlocker` hook instead of the provider
|
|
129
|
+
option, requests sent before the component that calls it renders.
|
|
130
|
+
Blocking starts in that component's first render, not the provider's.
|
|
131
|
+
|
|
132
|
+
Keep tracking calls out of that window:
|
|
133
|
+
|
|
134
|
+
* Send them from an effect or an event handler, never at module top level.
|
|
135
|
+
* Load vendor SDKs through `scripts` instead of a `<script>` tag or
|
|
136
|
+
`next/script`, so they wait for consent before they run at all.
|
|
137
|
+
* Check `useConsent('measurement')` (or the category you need) before you
|
|
138
|
+
call a vendor from your own code, and treat the blocker as a backstop.
|
|
139
|
+
|
|
140
|
+
## Verify
|
|
141
|
+
|
|
142
|
+
Open the browser's network panel, clear site data and reload. Requests
|
|
143
|
+
matching a rule should not appear until you grant their category; with
|
|
144
|
+
logging on, the console lists each blocked request. Reject, reload, and
|
|
145
|
+
check that they still do not appear. The
|
|
146
|
+
[verification guide](../../guides/verify-consent.md) covers automated checks.
|
|
@@ -61,3 +61,9 @@ already wrote. Add `clearOnRevocation` to `ConsentProvider.options` to remove
|
|
|
61
61
|
declared data when its category is denied. See
|
|
62
62
|
[clear on revocation](../../integrations/clear-on-revocation.md) for configuration
|
|
63
63
|
and browser limits.
|
|
64
|
+
|
|
65
|
+
## Shared lifecycle controls
|
|
66
|
+
|
|
67
|
+
See [shared consent controls](../../guides/shared-consent-controls.md) for external CMPs,
|
|
68
|
+
preference delegation, withdrawal reloads, and application events. These controls
|
|
69
|
+
use the same core runtime across frameworks.
|