@c15t/react 3.0.0-alpha.1 → 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.
Files changed (241) hide show
  1. package/AGENTS.md +9 -2
  2. package/dist/aggregate-components.js +1 -1
  3. package/dist/chunk-warming.js +1 -1
  4. package/dist/component-hooks/use-headless-iab-consent-ui.js +1 -1
  5. package/dist/component-hooks/use-panel-trigger.js +1 -1
  6. package/dist/components/consent-gate/atoms.js +1 -0
  7. package/dist/components/consent-gate/consent-gate.js +2 -0
  8. package/dist/components/consent-gate/index.js +2 -0
  9. package/dist/components/consent-preferences-link/consent-preferences-link.js +1 -1
  10. package/dist/components/iab-panel/atoms/purpose-item.js +1 -1
  11. package/dist/components/iab-panel/atoms/stack-item.js +1 -1
  12. package/dist/components/iab-panel/iab-panel.js +1 -1
  13. package/dist/components/iab-panel/index.js +1 -1
  14. package/dist/components/panel/atoms/overlay.js +1 -1
  15. package/dist/components/panel/atoms/root.js +1 -1
  16. package/dist/components/panel/index.js +1 -1
  17. package/dist/components/panel/panel.js +1 -1
  18. package/dist/components/panel-trigger/atoms/button.js +1 -1
  19. package/dist/components/panel-trigger/atoms/icon.js +1 -1
  20. package/dist/components/panel-trigger/atoms/root.js +1 -1
  21. package/dist/components/panel-trigger/atoms/trigger-icons.js +1 -0
  22. package/dist/components/panel-trigger/panel-trigger-toolbar.js +1 -1
  23. package/dist/components/preferences/atoms/accordion.js +1 -1
  24. package/dist/components/preferences/atoms/root.js +1 -1
  25. package/dist/components/preferences/atoms/vendor-list.js +1 -0
  26. package/dist/components/preferences/index.js +1 -1
  27. package/dist/components/prompt/policy-actions.js +1 -1
  28. package/dist/components/shared/primitives/button.js +1 -1
  29. package/dist/components/shared/ui/accordion/accordion.js +1 -1
  30. package/dist/components/shared/ui/collapsible/collapsible.js +1 -1
  31. package/dist/components/shared/ui/logo.js +1 -1
  32. package/dist/components/shared/ui/preference-item/preference-item.js +1 -1
  33. package/dist/components/shared/ui/switch/switch.js +1 -1
  34. package/dist/components/shared/ui/tabs/tabs.js +1 -1
  35. package/dist/consent-gate.js +2 -0
  36. package/dist/consent-theme.js +1 -0
  37. package/dist/draft.js +1 -1
  38. package/dist/external-iab-context.js +1 -1
  39. package/dist/headless.js +1 -1
  40. package/dist/hooks/use-committed-ref.js +2 -0
  41. package/dist/hooks.js +1 -1
  42. package/dist/iab/styles.tw3.css +1 -41
  43. package/dist/iab-context.js +1 -1
  44. package/dist/index.js +1 -1
  45. package/dist/kernel-selector.js +1 -0
  46. package/dist/module-hooks/iframe-blocker.js +1 -1
  47. package/dist/module-hooks/network-blocker.js +1 -1
  48. package/dist/module-hooks/network-hold.js +2 -0
  49. package/dist/module-hooks/persistence.js +1 -1
  50. package/dist/panel.js +1 -1
  51. package/dist/preferences.js +1 -2
  52. package/dist/provider.js +1 -1
  53. package/dist/styles.tw3.css +1 -43
  54. package/dist/ui-save.js +1 -1
  55. package/dist/utils/deferred-module.js +2 -0
  56. package/dist/version.js +1 -1
  57. package/dist-types/aggregate-components.d.ts +2 -2
  58. package/dist-types/chunk-warming.d.ts +41 -8
  59. package/dist-types/component-hooks/use-headless-iab-consent-ui.d.ts +2 -2
  60. package/dist-types/components/consent-gate/atoms.d.ts +11 -0
  61. package/dist-types/components/consent-gate/consent-gate.d.ts +2 -0
  62. package/dist-types/components/consent-gate/index.d.ts +41 -0
  63. package/dist-types/components/{frame → consent-gate}/types.d.ts +7 -7
  64. package/dist-types/components/consent-preferences-link/consent-preferences-link.d.ts +1 -1
  65. package/dist-types/components/consent-preferences-link/index.d.ts +1 -1
  66. package/dist-types/components/iab-panel/atoms/purpose-item.d.ts +5 -2
  67. package/dist-types/components/iab-panel/atoms/stack-item.d.ts +7 -2
  68. package/dist-types/components/iab-panel/atoms/vendor-list.d.ts +1 -1
  69. package/dist-types/components/iab-panel/iab-panel.d.ts +1 -1
  70. package/dist-types/components/iab-panel/index.d.ts +25 -24
  71. package/dist-types/components/iab-prompt/index.d.ts +17 -17
  72. package/dist-types/components/panel/atoms/card.d.ts +4 -4
  73. package/dist-types/components/panel/atoms/overlay.d.ts +1 -1
  74. package/dist-types/components/panel/index.d.ts +8 -7
  75. package/dist-types/components/panel/panel.d.ts +3 -3
  76. package/dist-types/components/panel-link/index.d.ts +1 -1
  77. package/dist-types/components/panel-trigger/atoms/button.d.ts +1 -1
  78. package/dist-types/components/panel-trigger/atoms/icon.d.ts +1 -1
  79. package/dist-types/components/panel-trigger/atoms/root.d.ts +2 -2
  80. package/dist-types/components/panel-trigger/atoms/toolbar.d.ts +2 -2
  81. package/dist-types/components/panel-trigger/atoms/trigger-icons.d.ts +20 -0
  82. package/dist-types/components/panel-trigger/index.d.ts +4 -4
  83. package/dist-types/components/panel-trigger/panel-trigger-toolbar.d.ts +1 -1
  84. package/dist-types/components/panel-trigger/panel-trigger.d.ts +9 -9
  85. package/dist-types/components/panel-trigger/types.d.ts +1 -1
  86. package/dist-types/components/preferences/atoms/accordion.d.ts +6 -4
  87. package/dist-types/components/preferences/atoms/button.d.ts +1 -1
  88. package/dist-types/components/preferences/atoms/footer.d.ts +1 -1
  89. package/dist-types/components/preferences/atoms/root.d.ts +1 -1
  90. package/dist-types/components/preferences/atoms/vendor-list.d.ts +23 -0
  91. package/dist-types/components/preferences/index.d.ts +14 -11
  92. package/dist-types/components/preferences/policy-actions.d.ts +2 -2
  93. package/dist-types/components/preferences/preferences.d.ts +1 -1
  94. package/dist-types/components/preferences/types.d.ts +1 -1
  95. package/dist-types/components/prompt/components.d.ts +3 -3
  96. package/dist-types/components/prompt/index.d.ts +11 -11
  97. package/dist-types/components/prompt/policy-actions.d.ts +2 -2
  98. package/dist-types/components/prompt/prompt.d.ts +1 -1
  99. package/dist-types/components/shared/policy-actions.d.ts +2 -2
  100. package/dist-types/components/shared/primitives/box.d.ts +1 -1
  101. package/dist-types/components/shared/primitives/button.d.ts +2 -2
  102. package/dist-types/components/shared/primitives/button.types.d.ts +1 -1
  103. package/dist-types/components/shared/ui/accordion/accordion.d.ts +3 -2
  104. package/dist-types/components/shared/ui/accordion/index.d.ts +2 -2
  105. package/dist-types/components/shared/ui/animated-collapse/index.d.ts +2 -2
  106. package/dist-types/components/shared/ui/branding.d.ts +1 -1
  107. package/dist-types/components/shared/ui/button/button.d.ts +1 -1
  108. package/dist-types/components/shared/ui/button/index.d.ts +2 -2
  109. package/dist-types/components/shared/ui/collapsible/collapsible.d.ts +1 -0
  110. package/dist-types/components/shared/ui/collapsible/index.d.ts +1 -1
  111. package/dist-types/components/shared/ui/dialog/index.d.ts +1 -1
  112. package/dist-types/components/shared/ui/logo.d.ts +0 -10
  113. package/dist-types/components/shared/ui/preference-item/index.d.ts +1 -1
  114. package/dist-types/components/shared/ui/preference-item/preference-item.d.ts +14 -0
  115. package/dist-types/components/shared/ui/switch/index.d.ts +2 -2
  116. package/dist-types/components/shared/ui/switch/switch.d.ts +2 -1
  117. package/dist-types/components/shared/ui/tabs/index.d.ts +1 -1
  118. package/dist-types/components/shared/ui/tabs/tabs.d.ts +1 -0
  119. package/dist-types/consent-gate.d.ts +1 -0
  120. package/dist-types/consent-theme.d.ts +38 -0
  121. package/dist-types/context/index.d.ts +3 -3
  122. package/dist-types/context/theme-context.d.ts +1 -1
  123. package/dist-types/draft.d.ts +96 -7
  124. package/dist-types/headless.d.ts +6 -7
  125. package/dist-types/hooks/use-committed-ref.d.ts +24 -0
  126. package/dist-types/hooks/use-headless-iab-consent-ui.d.ts +1 -1
  127. package/dist-types/hooks/use-iab-manager.d.ts +1 -1
  128. package/dist-types/hooks/use-theme.d.ts +1 -1
  129. package/dist-types/hooks.d.ts +30 -1
  130. package/dist-types/iab.d.ts +5 -5
  131. package/dist-types/index.d.ts +26 -25
  132. package/dist-types/kernel-selector.d.ts +66 -0
  133. package/dist-types/module-hooks/iframe-blocker.d.ts +27 -0
  134. package/dist-types/module-hooks/network-blocker.d.ts +23 -0
  135. package/dist-types/module-hooks/network-hold.d.ts +47 -0
  136. package/dist-types/module-hooks.d.ts +4 -4
  137. package/dist-types/panel-link.d.ts +1 -1
  138. package/dist-types/panel-trigger.d.ts +1 -1
  139. package/dist-types/panel.d.ts +13 -1
  140. package/dist-types/preferences.d.ts +12 -1
  141. package/dist-types/primitives/accordion.d.ts +1 -1
  142. package/dist-types/primitives/button.d.ts +1 -1
  143. package/dist-types/primitives/collapsible.d.ts +1 -1
  144. package/dist-types/primitives/dialog.d.ts +1 -1
  145. package/dist-types/primitives/preference-item.d.ts +1 -1
  146. package/dist-types/primitives/switch.d.ts +1 -1
  147. package/dist-types/primitives/tabs.d.ts +1 -1
  148. package/dist-types/primitives.d.ts +7 -7
  149. package/dist-types/prompt.d.ts +1 -1
  150. package/dist-types/provider.d.ts +62 -12
  151. package/dist-types/server/fetch-ssr-data.d.ts +1 -1
  152. package/dist-types/server/index.d.ts +5 -5
  153. package/dist-types/theme-provider.d.ts +2 -2
  154. package/dist-types/types/index.d.ts +3 -3
  155. package/dist-types/types/manager.d.ts +1 -1
  156. package/dist-types/types/theme/index.d.ts +1 -1
  157. package/dist-types/ui-config-context.d.ts +3 -1
  158. package/dist-types/ui-save.d.ts +21 -1
  159. package/dist-types/utils/deferred-module.d.ts +13 -0
  160. package/dist-types/utils/index.d.ts +4 -4
  161. package/dist-types/utils/merge-slot-props.d.ts +2 -2
  162. package/dist-types/utils/merge-styles.d.ts +1 -1
  163. package/dist-types/utils/theme-utils.d.ts +1 -1
  164. package/dist-types/version.d.ts +1 -1
  165. package/docs/README.md +9 -2
  166. package/docs/customization/overview.md +4 -3
  167. package/docs/customization/recipes.md +4 -2
  168. package/docs/customization/tokens.md +66 -3
  169. package/docs/frameworks/react/components/consent-dialog.md +46 -7
  170. package/docs/frameworks/react/components/{frame.md → consent-gate.md} +35 -31
  171. package/docs/frameworks/react/components/consent-manager-provider.md +35 -11
  172. package/docs/frameworks/react/components/consent-widget.md +48 -6
  173. package/docs/frameworks/react/concepts/consent-categories.md +38 -2
  174. package/docs/frameworks/react/hooks/{use-consent-manager/overview.md → overview.md} +7 -5
  175. package/docs/frameworks/react/iab/overview.md +121 -1
  176. package/docs/frameworks/react/network-blocker.md +146 -0
  177. package/docs/frameworks/react/script-loader.md +6 -0
  178. package/docs/frameworks/react/styling/overview.md +71 -22
  179. package/docs/guides/consent-state.md +327 -0
  180. package/docs/guides/deployment-modes.md +12 -0
  181. package/docs/guides/shared-consent-controls.md +158 -0
  182. package/docs/integrations/adobe-analytics.md +1 -1
  183. package/docs/integrations/ahrefs-analytics.md +1 -1
  184. package/docs/integrations/amplitude.md +1 -1
  185. package/docs/integrations/building-integrations.md +5 -0
  186. package/docs/integrations/clearbit.md +1 -1
  187. package/docs/integrations/cloudflare-web-analytics.md +1 -1
  188. package/docs/integrations/cloudflare-zaraz.md +399 -0
  189. package/docs/integrations/crisp.md +1 -1
  190. package/docs/integrations/databuddy.md +1 -1
  191. package/docs/integrations/fathom-analytics.md +1 -1
  192. package/docs/integrations/front-chat.md +322 -0
  193. package/docs/integrations/google-maps.md +21 -21
  194. package/docs/integrations/google-tag-manager.md +1 -1
  195. package/docs/integrations/google-tag.md +1 -1
  196. package/docs/integrations/granular-consent.md +210 -0
  197. package/docs/integrations/heap.md +1 -1
  198. package/docs/integrations/hightouch.md +1 -1
  199. package/docs/integrations/hotjar.md +1 -1
  200. package/docs/integrations/intercom.md +1 -1
  201. package/docs/integrations/linkedin-insights.md +1 -1
  202. package/docs/integrations/logrocket.md +1 -1
  203. package/docs/integrations/matomo-analytics.md +1 -1
  204. package/docs/integrations/meta-pixel.md +1 -1
  205. package/docs/integrations/microsoft-clarity.md +1 -1
  206. package/docs/integrations/microsoft-uet.md +1 -1
  207. package/docs/integrations/mixpanel-analytics.md +1 -1
  208. package/docs/integrations/one-dollar-stats.md +305 -0
  209. package/docs/integrations/openai-pixel.md +1 -1
  210. package/docs/integrations/overview.md +22 -18
  211. package/docs/integrations/pinterest-tag.md +321 -0
  212. package/docs/integrations/pirsch.md +1 -1
  213. package/docs/integrations/plausible-analytics.md +1 -1
  214. package/docs/integrations/posthog.md +1 -1
  215. package/docs/integrations/promptwatch.md +1 -1
  216. package/docs/integrations/reddit-pixel.md +1 -1
  217. package/docs/integrations/rudderstack.md +1 -1
  218. package/docs/integrations/rybbit-analytics.md +1 -1
  219. package/docs/integrations/segment.md +1 -1
  220. package/docs/integrations/snapchat-pixel.md +1 -1
  221. package/docs/integrations/tiktok-pixel.md +1 -1
  222. package/docs/integrations/umami-analytics.md +1 -1
  223. package/docs/integrations/vercel-analytics.md +1 -1
  224. package/docs/integrations/x-pixel.md +1 -1
  225. package/docs/integrations/youtube.md +27 -22
  226. package/docs/upgrade-v3.md +176 -1
  227. package/package.json +26 -16
  228. package/dist/component-hooks/use-manager.js +0 -2
  229. package/dist/components/frame/atoms.js +0 -1
  230. package/dist/components/frame/frame.js +0 -2
  231. package/dist/components/frame/index.js +0 -2
  232. package/dist/frame.js +0 -2
  233. package/dist-types/component-hooks/use-manager.d.ts +0 -40
  234. package/dist-types/components/frame/atoms.d.ts +0 -11
  235. package/dist-types/components/frame/frame.d.ts +0 -2
  236. package/dist-types/components/frame/index.d.ts +0 -24
  237. package/dist-types/frame.d.ts +0 -1
  238. package/readme.json +0 -48
  239. package/src/iab/styles.css +0 -11
  240. package/src/styles.css +0 -10
  241. /package/dist/components/{frame → consent-gate}/types.js +0 -0
@@ -93,18 +93,48 @@ it is `dialog`. These set that surface:
93
93
  * The Customize button on a `ConsentBanner`, and the "Manage preferences"
94
94
  or "Do not sell or share my data" button a notice renders.
95
95
  * `ConsentDialogLink` and `ConsentDialogTrigger`.
96
- * The button in a `Frame` placeholder.
96
+ * The button in a `ConsentGate` placeholder.
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. It downloads when the
101
- dialog first opens or when a visitor hovers or focuses a Customize button,
102
- and renders nothing until that chunk is ready. Passing `open={true}` or
103
- `showTrigger` loads it on mount.
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 after the save succeeds; a save that fails keeps it
107
- open. With `open={true}`, these actions leave the dialog visible; the
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.
@@ -1,14 +1,14 @@
1
1
  ---
2
- title: Frame
3
- description: Gate a YouTube or map iframe behind consent with Frame in React,
4
- showing a placeholder that opens the preference center until the category is
5
- allowed.
2
+ title: ConsentGate
3
+ description: Gate a YouTube or map iframe behind consent with ConsentGate in
4
+ React, showing a placeholder that opens the preference center until the
5
+ category is allowed.
6
6
  group: frameworks
7
7
  ---
8
8
 
9
9
  ## Gate an embed behind consent
10
10
 
11
- `Frame` mounts its children only while the effective permission for one
11
+ `ConsentGate` mounts its children only while the effective permission for one
12
12
  consent category is granted, and shows a placeholder otherwise. Wrap any
13
13
  iframe or third-party widget that would set cookies or contact a vendor on
14
14
  load. Render it anywhere inside the `ConsentProvider` from your
@@ -16,11 +16,11 @@ load. Render it anywhere inside the `ConsentProvider` from your
16
16
  placeholder button opens.
17
17
 
18
18
  ```tsx title="src/product-video.tsx"
19
- import { Frame } from 'c15t/react';
19
+ import { ConsentGate } from 'c15t/react';
20
20
 
21
21
  export function ProductVideo() {
22
22
  return (
23
- <Frame category="marketing" className="video-frame">
23
+ <ConsentGate category="marketing" className="video-frame">
24
24
  <iframe
25
25
  src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ"
26
26
  title="Product tour"
@@ -28,18 +28,18 @@ export function ProductVideo() {
28
28
  allowFullScreen
29
29
  style={{ width: '100%', aspectRatio: '16 / 9', border: 0 }}
30
30
  />
31
- </Frame>
31
+ </ConsentGate>
32
32
  );
33
33
  }
34
34
  ```
35
35
 
36
36
  `marketing` must be in your policy scope. If you set a non-empty
37
37
  `options.consentCategories` list on `ConsentProvider`, include `marketing`
38
- there too so the preference center can display and save it. `Frame` does
38
+ there too so the preference center can display and save it. `ConsentGate` does
39
39
  not add categories to that list.
40
40
 
41
41
  The placeholder names the category using its title from your translations. Set `className` or `style`
42
- on `Frame` to reserve the embed's space, so the page does not shift when the
42
+ on `ConsentGate` to reserve the embed's space, so the page does not shift when the
43
43
  placeholder is replaced.
44
44
 
45
45
  ## Props
@@ -51,22 +51,22 @@ placeholder is replaced.
51
51
  | `placeholder` | `ReactNode` | built-in placeholder | Replaces the built-in title and button while the category is not allowed. Falsy values such as `null`, `false`, `0` and an empty string use the built-in placeholder. Pass an empty fragment, `<></>`, to render no placeholder content. |
52
52
 
53
53
  Other `div` attributes such as `className`, `style` and `ref` apply to the
54
- wrapper element that stays in the page in both states. `FrameProps` also
54
+ wrapper element that stays in the page in both states. `ConsentGateProps` also
55
55
  declares `noStyle` and `theme`, but the component does not apply them yet;
56
56
  style the wrapper with `className` and theme the placeholder through the
57
57
  provider `theme` option.
58
58
 
59
59
  ## Behavior
60
60
 
61
- `Frame` renders a `div` wrapper. Inside it, when the effective permission for
61
+ `ConsentGate` renders a `div` wrapper. Inside it, when the effective permission for
62
62
  `category` is granted, it renders the children; otherwise it renders the
63
63
  placeholder. Permission is the same value `useConsent(category)` returns,
64
64
  so it can be granted under an opt-out rule before the visitor records a
65
65
  choice, and a recorded grant can be overridden by a privacy signal.
66
66
 
67
67
  While effective permission is denied, the children are absent from the DOM,
68
- so an iframe or a third-party widget inside `Frame` sends no requests. When the visitor later revokes it, the
69
- children unmount and the embed disappears. `Frame` does not depend on the
68
+ so an iframe or a third-party widget inside `ConsentGate` sends no requests. When the visitor later revokes it, the
69
+ children unmount and the embed disappears. `ConsentGate` does not depend on the
70
70
  network blocker or the iframe blocker modules; use those for markup you
71
71
  cannot wrap in a component.
72
72
 
@@ -84,18 +84,20 @@ 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 children or placeholder according to effective permission.
88
- A grant in the request cookie allows the embed only when policy and privacy
89
- signals permit it. Browser privacy signals detected during hydration can
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
- `Frame` shows the placeholder first and swaps in the embed after resolution
94
+ `ConsentGate` shows the placeholder first and swaps in the embed after resolution
93
95
  when the category is already granted.
94
96
 
95
- `Frame` is one component on this page; the per-vendor embed guides for
97
+ `ConsentGate` is one component on this page; the per-vendor embed guides for
96
98
  [YouTube](../../../integrations/youtube.md) and
97
99
  [Google Maps](../../../integrations/google-maps.md) show a complete embed
98
- configuration with sizing, titles and the same `Frame` usage across
100
+ configuration with sizing, titles and the same `ConsentGate` usage across
99
101
  frameworks.
100
102
 
101
103
  To read the same permission in your own components, use `useConsent` from
@@ -104,24 +106,24 @@ also import from there.
104
106
 
105
107
  ## Composition
106
108
 
107
- The placeholder parts are available as `Frame.Root`, `Frame.Title` and
108
- `Frame.Button` for a custom placeholder that keeps the built-in copy and
109
- behavior. `Frame.Title` and `Frame.Button` accept `category` and fill in the
109
+ The placeholder parts are available as `ConsentGate.Root`, `ConsentGate.Title` and
110
+ `ConsentGate.Button` for a custom placeholder that keeps the built-in copy and
111
+ behavior. `ConsentGate.Title` and `ConsentGate.Button` accept `category` and fill in the
110
112
  translated text; pass children to either to replace it.
111
113
 
112
114
  ```tsx
113
- <Frame
115
+ <ConsentGate
114
116
  category="marketing"
115
117
  placeholder={
116
- <Frame.Root>
117
- <Frame.Title category="marketing" />
118
+ <ConsentGate.Root>
119
+ <ConsentGate.Title category="marketing" />
118
120
  <p>The video is also available on our channel.</p>
119
- <Frame.Button category="marketing">Choose cookies</Frame.Button>
120
- </Frame.Root>
121
+ <ConsentGate.Button category="marketing">Choose cookies</ConsentGate.Button>
122
+ </ConsentGate.Root>
121
123
  }
122
124
  >
123
125
  <iframe src="https://www.youtube-nocookie.com/embed/..." title="Product tour" />
124
- </Frame>
126
+ </ConsentGate>
125
127
  ```
126
128
 
127
129
  The built-in placeholder carries `data-testid="frame-placeholder"` and its
@@ -131,7 +133,7 @@ button `data-testid="frame-open-dialog"`.
131
133
 
132
134
  The placeholder is plain text and a `button`, so it is readable and
133
135
  operable without the embed. Give the iframe a descriptive `title`, and keep a
134
- transcript, address or link outside the `Frame` for visitors who decline
136
+ transcript, address or link outside the `ConsentGate` for visitors who decline
135
137
  the category. A denied category may be fixed by policy, so opening the
136
138
  preference center does not guarantee that the visitor can grant it.
137
139
 
@@ -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 or the embed for a granted category. Browser-only initialization
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 | 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` | Theme tokens and component slot attributes |
44
- | `presentation` | Prompt and preferences layout behavior |
45
- | `i18n` | Languages and message overrides |
46
- | `storageConfig` | Browser record storage configuration |
47
- | `enabled` | Set false only when intentionally bypassing consent enforcement |
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,9 +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. See [consent state](../../../guides/consent-state.md) and
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.
71
+ See [consent state](../../../guides/consent-state.md) and
62
72
  [deployment modes](../../../guides/deployment-modes.md).
63
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
+
64
88
  ## Clear data when consent is denied
65
89
 
66
90
  Set `options.clearOnRevocation` to declare cookies, localStorage keys, and
@@ -85,6 +85,31 @@ Privacy Control overrides shows a note under its row: "Your saved choice is
85
85
  restricted by the current privacy settings." The switch still reflects the
86
86
  saved value; the effective permission is off.
87
87
 
88
+ ### Vendors
89
+
90
+ When the provider declares `vendors`, each category's expanded description
91
+ ends with one card per vendor whose category condition names that category,
92
+ styled like a category row: the vendor's name and a switch on the header,
93
+ and its description and privacy policy link behind the card's own expand
94
+ control. Cards start collapsed, so a long list stays one line per vendor,
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.
99
+ Vendor switches edit the same draft as the category
100
+ switches and record on Save. Saving with one vendor off denies that vendor
101
+ only; the category stays granted and its other vendors keep loading. While
102
+ the category is off in the draft the vendor switches are disabled, with the
103
+ hint "Turn on this category to choose vendors." A vendor declared
104
+ `disabled`, or one that only falls under `necessary`, is listed without a
105
+ switch. Accept all and Reject all clear every vendor denial, including one
106
+ staged but not yet saved. Under an `iab` policy no vendor rows render.
107
+ Vendors that are only referenced by a script slug, without a name and
108
+ privacy policy URL, still gate loading but are not listed, and so does a
109
+ vendor whose condition negates a category, since no category row can
110
+ host it. See
111
+ [granular consent](../../../integrations/granular-consent.md).
112
+
88
113
  ### Actions
89
114
 
90
115
  The preference center always renders Reject all, Accept all and Save, with
@@ -102,8 +127,12 @@ such as `['disclosure']`, renders the widget and Save completes without
102
127
  writing a consent record.
103
128
 
104
129
  For a fully custom preference center, `useConsentDraft()` returns the same
105
- draft: `values`, `displayedCategories`, `isDirty`, `isStale`, `set`,
106
- `update`, `acceptAll`, `rejectAll`, `save` and `reset`.
130
+ draft: `values`, `displayedCategories`, `vendors`, `isDirty`, `isStale`,
131
+ `set`, `update`, `setVendor`, `acceptAll`, `rejectAll`, `save` and `reset`.
132
+ A custom vendor control alone takes `useVendorDraft()`, the vendor slice of
133
+ that draft: `vendors`, `setVendor`, `isDirty`, `isStale`, `save` and `reset`.
134
+ `useDeclaredVendors()`, `useVendorChoice()` and `useVendorAllowed(id)` read
135
+ the declared list, the recorded denials and one vendor's effective result.
107
136
 
108
137
  `useConsentDraft` and `ConsentDraftProvider` import from `c15t/react`.
109
138
 
@@ -112,7 +141,10 @@ draft: `values`, `displayedCategories`, `isDirty`, `isStale`, `set`,
112
141
  Each row is a disclosure: a button that expands the category description
113
142
  and a switch beside it, so a visitor can read the description without
114
143
  changing the choice. Each switch has the category title as its accessible
115
- name. The policy-change alert uses `role="alert"`, and a restriction note is
144
+ name. A category's vendor cards sit in a `region` named "Vendors" with the
145
+ count; each card's expand button carries `aria-expanded`, each vendor
146
+ switch is named "Allow" followed by the vendor name, and is described by
147
+ the vendor's name element. The policy-change alert uses `role="alert"`, and a restriction note is
116
148
  an `output` element linked to its switch through `aria-describedby`. The
117
149
  root's `dir` attribute follows the active language.
118
150
 
@@ -122,7 +154,8 @@ Every part is available as `ConsentWidget.<Part>`. `Root` provides the
122
154
  draft and accepts `noStyle`, `disableAnimation` and `uiSource`. `Accordion`
123
155
  and `AccordionItems` render the stock rows; `AccordionItem`,
124
156
  `AccordionTrigger`, `AccordionTriggerInner`, `AccordionContent`,
125
- `AccordionArrow` and `Switch` build your own. `PolicyActions` renders the
157
+ `AccordionArrow` and `Switch` build your own; `VendorList` renders one
158
+ category's vendor rows and takes a `category` prop. `PolicyActions` renders the
126
159
  resolved actions in `Footer` and `FooterSubGroup` and accepts `renderAction`
127
160
  to replace one button; `AcceptAllButton`, `RejectButton`, `SaveButton` and
128
161
  `CustomizeButton` are the individual buttons.
@@ -155,8 +188,17 @@ export function PreferenceCenter() {
155
188
  Provider component slots for the stock structure are `manager.root`,
156
189
  `manager.footer`, `manager.actionGroup`, `accordion.root`,
157
190
  `accordion.triggerRow`, `accordion.title`, `accordion.control`,
158
- `accordion-item.root`, `accordion-item.trigger`, `accordion-item.content`
159
- and `tag.manager`.
191
+ `accordion-item.root`, `accordion-item.trigger`, `accordion-item.content`,
192
+ `vendor-list.root`, `vendor-list.trigger`, `vendor-list.content`,
193
+ `vendor-list.item`, `vendor-list.header`,
194
+ `vendor-list.name`, `vendor-list.description`, `vendor-list.link`,
195
+ `vendor-list.control` and `tag.manager`.
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.
160
202
 
161
203
  The compound parts import from `c15t/react` as properties of `ConsentWidget`.
162
204
  See [Styling](../styling/overview.md) for slots and tokens.
@@ -17,8 +17,23 @@ group: frameworks
17
17
 
18
18
  `necessary` is always permitted. Assign categories based on what an integration
19
19
  does in your application; renaming analytics to necessary does not change its
20
- purpose. The provider's displayed categories and the resolved policy scope must
21
- agree.
20
+ purpose.
21
+
22
+ c15t discovers categories from registered scripts, network rules, React `ConsentGate`
23
+ components, and iframes with `data-category` handled by the iframe blocker.
24
+ The browser client also discovers inert scripts tagged with `data-c15t-category`.
25
+ Compound script and network conditions contribute every category they reference.
26
+
27
+ Discovered categories are added to `consentCategories`, when supplied. The dialog
28
+ always includes Necessary and offers the optional categories in that combined
29
+ set that are also in the resolved policy scope. Choice completion uses the same
30
+ set. If neither configuration nor integrations supply categories, the dialog
31
+ uses the full policy scope.
32
+
33
+ Categories discovered later are added immediately and retained until the provider
34
+ or runtime is recreated. Adding a category can require a new choice; removing a
35
+ script or unmounting a frame does not remove its category or erase consent.
36
+ Discovery does not grant consent or change backend permission restrictions.
22
37
 
23
38
  ## Respect policy scope
24
39
 
@@ -28,6 +43,27 @@ only some optional categories, set `scopeMode` explicitly. An omitted scope,
28
43
  `['*']`, or a list containing only `necessary` expands to the default optional
29
44
  categories; a necessary-only list is not a shortcut for disabling all tracking.
30
45
 
46
+ For example, a backend rule with `categories: ['necessary']` and scripts assigned
47
+ to `marketing` and `measurement` displays Necessary, Marketing, and Analytics,
48
+ even without `consentCategories`. Accept All records both optional choices and
49
+ keeps the banner dismissed after reload. An explicit list of
50
+ `['necessary', 'measurement']` with no other integrations displays Necessary and
51
+ Analytics.
52
+
53
+ Hidden optional categories do not need a choice to dismiss the banner. Their
54
+ permissions still follow the backend policy, so hidden opt-in categories remain
55
+ denied without a valid grant. React providers enable DOM iframe blocking and
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
64
+ `iframeBlocker: { disableAutomaticBlocking: true }` to scan manually with
65
+ `useIframeBlocker({ disableAutomaticBlocking: true }).processAllIframes()`.
66
+
31
67
  Use effective permissions to gate work and explicit choices to inspect what the
32
68
  visitor confirmed. Read [consent state](../../../guides/consent-state.md) for that
33
69
  distinction and [migration](../../../upgrade-v3.md) for v3 policy configuration.
@@ -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
- `useConsentManager` remains available for the combined UI API, including draft
55
- management. Prefer focused hooks for simple feature gates and use the headless
56
- UI hook when implementing a complete custom prompt. Read
57
- [consent state](../../../../guides/consent-state.md) before treating a callback as evidence
58
- of a choice.
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.