@c15t/react 3.0.0-alpha.1 → 3.0.0-alpha.2

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 (141) hide show
  1. package/AGENTS.md +3 -1
  2. package/dist/aggregate-components.js +1 -1
  3. package/dist/component-hooks/use-manager.js +1 -1
  4. package/dist/components/consent-gate/atoms.js +1 -0
  5. package/dist/components/consent-gate/consent-gate.js +2 -0
  6. package/dist/components/consent-gate/index.js +2 -0
  7. package/dist/components/preferences/atoms/accordion.js +1 -1
  8. package/dist/components/preferences/atoms/vendor-list.js +1 -0
  9. package/dist/components/preferences/index.js +1 -1
  10. package/dist/consent-gate.js +2 -0
  11. package/dist/draft.js +1 -1
  12. package/dist/external-iab-context.js +1 -1
  13. package/dist/hooks.js +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/provider.js +1 -1
  16. package/dist/styles.tw3.css +6 -3
  17. package/dist/utils/deferred-module.js +2 -0
  18. package/dist/version.js +1 -1
  19. package/dist-types/aggregate-components.d.ts +2 -2
  20. package/dist-types/component-hooks/use-headless-iab-consent-ui.d.ts +1 -1
  21. package/dist-types/component-hooks/use-manager.d.ts +6 -1
  22. package/dist-types/components/consent-gate/atoms.d.ts +11 -0
  23. package/dist-types/components/consent-gate/consent-gate.d.ts +2 -0
  24. package/dist-types/components/consent-gate/index.d.ts +41 -0
  25. package/dist-types/components/{frame → consent-gate}/types.d.ts +7 -7
  26. package/dist-types/components/consent-preferences-link/consent-preferences-link.d.ts +1 -1
  27. package/dist-types/components/consent-preferences-link/index.d.ts +1 -1
  28. package/dist-types/components/iab-panel/atoms/purpose-item.d.ts +1 -1
  29. package/dist-types/components/iab-panel/atoms/stack-item.d.ts +1 -1
  30. package/dist-types/components/iab-panel/atoms/vendor-list.d.ts +1 -1
  31. package/dist-types/components/iab-panel/iab-panel.d.ts +1 -1
  32. package/dist-types/components/iab-panel/index.d.ts +24 -24
  33. package/dist-types/components/iab-prompt/index.d.ts +17 -17
  34. package/dist-types/components/panel/atoms/card.d.ts +4 -4
  35. package/dist-types/components/panel/atoms/overlay.d.ts +1 -1
  36. package/dist-types/components/panel/index.d.ts +7 -7
  37. package/dist-types/components/panel/panel.d.ts +2 -2
  38. package/dist-types/components/panel-link/index.d.ts +1 -1
  39. package/dist-types/components/panel-trigger/atoms/button.d.ts +1 -1
  40. package/dist-types/components/panel-trigger/atoms/icon.d.ts +1 -1
  41. package/dist-types/components/panel-trigger/atoms/root.d.ts +2 -2
  42. package/dist-types/components/panel-trigger/atoms/toolbar.d.ts +2 -2
  43. package/dist-types/components/panel-trigger/index.d.ts +4 -4
  44. package/dist-types/components/panel-trigger/panel-trigger-toolbar.d.ts +1 -1
  45. package/dist-types/components/panel-trigger/panel-trigger.d.ts +9 -9
  46. package/dist-types/components/panel-trigger/types.d.ts +1 -1
  47. package/dist-types/components/preferences/atoms/accordion.d.ts +6 -4
  48. package/dist-types/components/preferences/atoms/button.d.ts +1 -1
  49. package/dist-types/components/preferences/atoms/footer.d.ts +1 -1
  50. package/dist-types/components/preferences/atoms/root.d.ts +1 -1
  51. package/dist-types/components/preferences/atoms/vendor-list.d.ts +23 -0
  52. package/dist-types/components/preferences/index.d.ts +13 -11
  53. package/dist-types/components/preferences/policy-actions.d.ts +2 -2
  54. package/dist-types/components/preferences/preferences.d.ts +1 -1
  55. package/dist-types/components/preferences/types.d.ts +1 -1
  56. package/dist-types/components/prompt/components.d.ts +3 -3
  57. package/dist-types/components/prompt/index.d.ts +11 -11
  58. package/dist-types/components/prompt/policy-actions.d.ts +2 -2
  59. package/dist-types/components/prompt/prompt.d.ts +1 -1
  60. package/dist-types/components/shared/policy-actions.d.ts +2 -2
  61. package/dist-types/components/shared/primitives/box.d.ts +1 -1
  62. package/dist-types/components/shared/primitives/button.d.ts +2 -2
  63. package/dist-types/components/shared/primitives/button.types.d.ts +1 -1
  64. package/dist-types/components/shared/ui/accordion/accordion.d.ts +2 -2
  65. package/dist-types/components/shared/ui/accordion/index.d.ts +2 -2
  66. package/dist-types/components/shared/ui/animated-collapse/index.d.ts +2 -2
  67. package/dist-types/components/shared/ui/branding.d.ts +1 -1
  68. package/dist-types/components/shared/ui/button/button.d.ts +1 -1
  69. package/dist-types/components/shared/ui/button/index.d.ts +2 -2
  70. package/dist-types/components/shared/ui/collapsible/index.d.ts +1 -1
  71. package/dist-types/components/shared/ui/dialog/index.d.ts +1 -1
  72. package/dist-types/components/shared/ui/preference-item/index.d.ts +1 -1
  73. package/dist-types/components/shared/ui/switch/index.d.ts +2 -2
  74. package/dist-types/components/shared/ui/switch/switch.d.ts +1 -1
  75. package/dist-types/components/shared/ui/tabs/index.d.ts +1 -1
  76. package/dist-types/consent-gate.d.ts +1 -0
  77. package/dist-types/context/index.d.ts +3 -3
  78. package/dist-types/context/theme-context.d.ts +1 -1
  79. package/dist-types/draft.d.ts +53 -1
  80. package/dist-types/headless.d.ts +7 -7
  81. package/dist-types/hooks/use-headless-iab-consent-ui.d.ts +1 -1
  82. package/dist-types/hooks/use-iab-manager.d.ts +1 -1
  83. package/dist-types/hooks/use-theme.d.ts +1 -1
  84. package/dist-types/hooks.d.ts +28 -1
  85. package/dist-types/iab.d.ts +5 -5
  86. package/dist-types/index.d.ts +25 -25
  87. package/dist-types/module-hooks.d.ts +4 -4
  88. package/dist-types/panel-link.d.ts +1 -1
  89. package/dist-types/panel-trigger.d.ts +1 -1
  90. package/dist-types/panel.d.ts +1 -1
  91. package/dist-types/preferences.d.ts +1 -1
  92. package/dist-types/primitives/accordion.d.ts +1 -1
  93. package/dist-types/primitives/button.d.ts +1 -1
  94. package/dist-types/primitives/collapsible.d.ts +1 -1
  95. package/dist-types/primitives/dialog.d.ts +1 -1
  96. package/dist-types/primitives/preference-item.d.ts +1 -1
  97. package/dist-types/primitives/switch.d.ts +1 -1
  98. package/dist-types/primitives/tabs.d.ts +1 -1
  99. package/dist-types/primitives.d.ts +7 -7
  100. package/dist-types/prompt.d.ts +1 -1
  101. package/dist-types/provider.d.ts +22 -5
  102. package/dist-types/server/fetch-ssr-data.d.ts +1 -1
  103. package/dist-types/server/index.d.ts +5 -5
  104. package/dist-types/theme-provider.d.ts +2 -2
  105. package/dist-types/types/index.d.ts +3 -3
  106. package/dist-types/types/manager.d.ts +1 -1
  107. package/dist-types/types/theme/index.d.ts +1 -1
  108. package/dist-types/ui-config-context.d.ts +1 -1
  109. package/dist-types/utils/deferred-module.d.ts +11 -0
  110. package/dist-types/utils/index.d.ts +4 -4
  111. package/dist-types/utils/merge-slot-props.d.ts +2 -2
  112. package/dist-types/utils/merge-styles.d.ts +1 -1
  113. package/dist-types/utils/theme-utils.d.ts +1 -1
  114. package/dist-types/version.d.ts +1 -1
  115. package/docs/README.md +3 -1
  116. package/docs/frameworks/react/components/consent-dialog.md +1 -1
  117. package/docs/frameworks/react/components/{frame.md → consent-gate.md} +27 -27
  118. package/docs/frameworks/react/components/consent-manager-provider.md +5 -1
  119. package/docs/frameworks/react/components/consent-widget.md +39 -6
  120. package/docs/frameworks/react/concepts/consent-categories.md +31 -2
  121. package/docs/guides/deployment-modes.md +12 -0
  122. package/docs/integrations/building-integrations.md +5 -0
  123. package/docs/integrations/cloudflare-zaraz.md +399 -0
  124. package/docs/integrations/google-maps.md +20 -20
  125. package/docs/integrations/granular-consent.md +208 -0
  126. package/docs/integrations/overview.md +5 -4
  127. package/docs/integrations/youtube.md +26 -21
  128. package/docs/upgrade-v3.md +47 -0
  129. package/package.json +26 -16
  130. package/dist/components/frame/atoms.js +0 -1
  131. package/dist/components/frame/frame.js +0 -2
  132. package/dist/components/frame/index.js +0 -2
  133. package/dist/frame.js +0 -2
  134. package/dist-types/components/frame/atoms.d.ts +0 -11
  135. package/dist-types/components/frame/frame.d.ts +0 -2
  136. package/dist-types/components/frame/index.d.ts +0 -24
  137. package/dist-types/frame.d.ts +0 -1
  138. package/readme.json +0 -48
  139. package/src/iab/styles.css +0 -11
  140. package/src/styles.css +0 -10
  141. /package/dist/components/{frame → consent-gate}/types.js +0 -0
package/docs/README.md CHANGED
@@ -25,10 +25,10 @@ These docs describe v3. Start with Inth hosted setup, identify the framework, ro
25
25
  - [ConsentDialog](./frameworks/react/components/consent-dialog.md): Open the c15t preference center as a modal ConsentDialog in a React app, wire its triggers and control blocking, focus and policy gating.
26
26
  - [ConsentDialogLink](./frameworks/react/components/consent-dialog-link.md): Add a ConsentDialogLink to a React footer so visitors reopen the c15t preference center from your own text link, with asChild and rights data.
27
27
  - [ConsentDialogTrigger](./frameworks/react/components/consent-dialog-trigger.md): Floating button and toolbar that reopen the preference center after the first prompt.
28
+ - [ConsentGate](./frameworks/react/components/consent-gate.md): Gate a YouTube or map iframe behind consent with ConsentGate in React, showing a placeholder that opens the preference center until the category is allowed.
28
29
  - [ConsentProvider](./frameworks/react/components/consent-manager-provider.md): Configure the v3 consent runtime and share its state with child components.
29
30
  - [ConsentWidget](./frameworks/react/components/consent-widget.md): Embed the c15t preference center inline with ConsentWidget on a React privacy page, with draft toggles that record a choice only on Save.
30
31
  - [DevTools](./frameworks/react/components/dev-tools.md): A development tool for inspecting consent state, geolocation, loaded scripts, and consent events in real time.
31
- - [Frame](./frameworks/react/components/frame.md): Gate a YouTube or map iframe behind consent with Frame in React, showing a placeholder that opens the preference center until the category is allowed.
32
32
  - [Consent categories](./frameworks/react/concepts/consent-categories.md): Assign optional features to categories and understand how policy scope affects permission.
33
33
  - [Policy presets](./frameworks/react/concepts/policy-presets.md): Understand how policy rules affect React prompts, permissions and persistent privacy controls.
34
34
  - [Headless](./frameworks/react/headless.md): Build a custom consent banner in React with the c15t/react/headless hooks inside your ConsentProvider.
@@ -64,12 +64,14 @@ These docs describe v3. Start with Inth hosted setup, identify the framework, ro
64
64
  - [Clear on revocation](./integrations/clear-on-revocation.md): Remove configured first-party cookies and Web Storage keys when their consent category is denied.
65
65
  - [Clearbit](./integrations/clearbit.md): Configure Clearbit with c15t v3, understand marketing permission and verify loading and revocation.
66
66
  - [Cloudflare Web Analytics](./integrations/cloudflare-web-analytics.md): Configure Cloudflare Web Analytics with c15t v3, understand measurement permission and verify loading and revocation.
67
+ - [Cloudflare Zaraz](./integrations/cloudflare-zaraz.md): Synchronize c15t permissions with Zaraz purposes while Cloudflare manages your tools.
67
68
  - [Crisp](./integrations/crisp.md): Configure Crisp with c15t v3, understand functionality permission and verify loading and revocation.
68
69
  - [Databuddy](./integrations/databuddy.md): Configure Databuddy's initial and updated consent state with c15t v3.
69
70
  - [Fathom Analytics](./integrations/fathom-analytics.md): Configure Fathom Analytics with c15t v3, understand measurement permission and verify loading and revocation.
70
71
  - [Google Maps](./integrations/google-maps.md): Prevent a map iframe from mounting before the required permission.
71
72
  - [Google Tag](./integrations/google-tag.md): Configure gtag with c15t Consent Mode signals and understand its loading behavior.
72
73
  - [Google Tag Manager](./integrations/google-tag-manager.md): Load GTM with c15t consent signals and verify the tags inside your container.
74
+ - [Granular consent](./integrations/granular-consent.md): Let visitors grant a category and still turn one vendor off, without adopting IAB TCF.
73
75
  - [Heap](./integrations/heap.md): Configure Heap with c15t v3, understand measurement permission and verify loading and revocation.
74
76
  - [Hightouch](./integrations/hightouch.md): Configure Hightouch with c15t v3, understand measurement permission and verify loading and revocation.
75
77
  - [Hotjar](./integrations/hotjar.md): Configure Hotjar with c15t v3, understand measurement permission and verify loading and revocation.
@@ -93,7 +93,7 @@ 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
 
@@ -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
 
@@ -89,13 +89,13 @@ A grant in the request cookie allows the embed only when policy and privacy
89
89
  signals permit it. Browser privacy signals detected during hydration can
90
90
  withdraw that permission. In a browser-only setup the
91
91
  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
92
+ `ConsentGate` shows the placeholder first and swaps in the embed after resolution
93
93
  when the category is already granted.
94
94
 
95
- `Frame` is one component on this page; the per-vendor embed guides for
95
+ `ConsentGate` is one component on this page; the per-vendor embed guides for
96
96
  [YouTube](../../../integrations/youtube.md) and
97
97
  [Google Maps](../../../integrations/google-maps.md) show a complete embed
98
- configuration with sizing, titles and the same `Frame` usage across
98
+ configuration with sizing, titles and the same `ConsentGate` usage across
99
99
  frameworks.
100
100
 
101
101
  To read the same permission in your own components, use `useConsent` from
@@ -104,24 +104,24 @@ also import from there.
104
104
 
105
105
  ## Composition
106
106
 
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
107
+ The placeholder parts are available as `ConsentGate.Root`, `ConsentGate.Title` and
108
+ `ConsentGate.Button` for a custom placeholder that keeps the built-in copy and
109
+ behavior. `ConsentGate.Title` and `ConsentGate.Button` accept `category` and fill in the
110
110
  translated text; pass children to either to replace it.
111
111
 
112
112
  ```tsx
113
- <Frame
113
+ <ConsentGate
114
114
  category="marketing"
115
115
  placeholder={
116
- <Frame.Root>
117
- <Frame.Title category="marketing" />
116
+ <ConsentGate.Root>
117
+ <ConsentGate.Title category="marketing" />
118
118
  <p>The video is also available on our channel.</p>
119
- <Frame.Button category="marketing">Choose cookies</Frame.Button>
120
- </Frame.Root>
119
+ <ConsentGate.Button category="marketing">Choose cookies</ConsentGate.Button>
120
+ </ConsentGate.Root>
121
121
  }
122
122
  >
123
123
  <iframe src="https://www.youtube-nocookie.com/embed/..." title="Product tour" />
124
- </Frame>
124
+ </ConsentGate>
125
125
  ```
126
126
 
127
127
  The built-in placeholder carries `data-testid="frame-placeholder"` and its
@@ -131,7 +131,7 @@ button `data-testid="frame-open-dialog"`.
131
131
 
132
132
  The placeholder is plain text and a `button`, so it is readable and
133
133
  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
134
+ transcript, address or link outside the `ConsentGate` for visitors who decline
135
135
  the category. A denied category may be fixed by policy, so opening the
136
136
  preference center does not guarantee that the visitor can grant it.
137
137
 
@@ -58,7 +58,11 @@ signals. Hydration and notice dismissal do not become explicit choices.
58
58
 
59
59
  An externally owned runtime can be passed through `runtime`. Its owner must
60
60
  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
61
+ dispose a second copy. Keep the runtime stable for the provider's lifetime and
62
+ unmount the borrowing provider when disposing the runtime. IAB actions taken
63
+ before its handle is ready wait for that handle. If the provider unmounts first,
64
+ a pending `save()` rejects with an `AbortError` instead of waiting indefinitely.
65
+ See [consent state](../../../guides/consent-state.md) and
62
66
  [deployment modes](../../../guides/deployment-modes.md).
63
67
 
64
68
  ## Clear data when consent is denied
@@ -85,6 +85,28 @@ 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
+ Vendor switches edit the same draft as the category
97
+ switches and record on Save. Saving with one vendor off denies that vendor
98
+ only; the category stays granted and its other vendors keep loading. While
99
+ the category is off in the draft the vendor switches are disabled, with the
100
+ hint "Turn on this category to choose vendors." A vendor declared
101
+ `disabled`, or one that only falls under `necessary`, is listed without a
102
+ switch. Accept all and Reject all clear every vendor denial, including one
103
+ staged but not yet saved. Under an `iab` policy no vendor rows render.
104
+ Vendors that are only referenced by a script slug, without a name and
105
+ privacy policy URL, still gate loading but are not listed, and so does a
106
+ vendor whose condition negates a category, since no category row can
107
+ host it. See
108
+ [granular consent](../../../integrations/granular-consent.md).
109
+
88
110
  ### Actions
89
111
 
90
112
  The preference center always renders Reject all, Accept all and Save, with
@@ -102,8 +124,12 @@ such as `['disclosure']`, renders the widget and Save completes without
102
124
  writing a consent record.
103
125
 
104
126
  For a fully custom preference center, `useConsentDraft()` returns the same
105
- draft: `values`, `displayedCategories`, `isDirty`, `isStale`, `set`,
106
- `update`, `acceptAll`, `rejectAll`, `save` and `reset`.
127
+ draft: `values`, `displayedCategories`, `vendors`, `isDirty`, `isStale`,
128
+ `set`, `update`, `setVendor`, `acceptAll`, `rejectAll`, `save` and `reset`.
129
+ A custom vendor control alone takes `useVendorDraft()`, the vendor slice of
130
+ that draft: `vendors`, `setVendor`, `isDirty`, `isStale`, `save` and `reset`.
131
+ `useDeclaredVendors()`, `useVendorChoice()` and `useVendorAllowed(id)` read
132
+ the declared list, the recorded denials and one vendor's effective result.
107
133
 
108
134
  `useConsentDraft` and `ConsentDraftProvider` import from `c15t/react`.
109
135
 
@@ -112,7 +138,10 @@ draft: `values`, `displayedCategories`, `isDirty`, `isStale`, `set`,
112
138
  Each row is a disclosure: a button that expands the category description
113
139
  and a switch beside it, so a visitor can read the description without
114
140
  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
141
+ name. A category's vendor cards sit in a `region` named "Vendors" with the
142
+ count; each card's expand button carries `aria-expanded`, each vendor
143
+ switch is named "Allow" followed by the vendor name, and is described by
144
+ the vendor's name element. The policy-change alert uses `role="alert"`, and a restriction note is
116
145
  an `output` element linked to its switch through `aria-describedby`. The
117
146
  root's `dir` attribute follows the active language.
118
147
 
@@ -122,7 +151,8 @@ Every part is available as `ConsentWidget.<Part>`. `Root` provides the
122
151
  draft and accepts `noStyle`, `disableAnimation` and `uiSource`. `Accordion`
123
152
  and `AccordionItems` render the stock rows; `AccordionItem`,
124
153
  `AccordionTrigger`, `AccordionTriggerInner`, `AccordionContent`,
125
- `AccordionArrow` and `Switch` build your own. `PolicyActions` renders the
154
+ `AccordionArrow` and `Switch` build your own; `VendorList` renders one
155
+ category's vendor rows and takes a `category` prop. `PolicyActions` renders the
126
156
  resolved actions in `Footer` and `FooterSubGroup` and accepts `renderAction`
127
157
  to replace one button; `AcceptAllButton`, `RejectButton`, `SaveButton` and
128
158
  `CustomizeButton` are the individual buttons.
@@ -155,8 +185,11 @@ export function PreferenceCenter() {
155
185
  Provider component slots for the stock structure are `manager.root`,
156
186
  `manager.footer`, `manager.actionGroup`, `accordion.root`,
157
187
  `accordion.triggerRow`, `accordion.title`, `accordion.control`,
158
- `accordion-item.root`, `accordion-item.trigger`, `accordion-item.content`
159
- and `tag.manager`.
188
+ `accordion-item.root`, `accordion-item.trigger`, `accordion-item.content`,
189
+ `vendor-list.root`, `vendor-list.trigger`, `vendor-list.content`,
190
+ `vendor-list.item`, `vendor-list.header`,
191
+ `vendor-list.name`, `vendor-list.description`, `vendor-list.link`,
192
+ `vendor-list.control` and `tag.manager`.
160
193
 
161
194
  The compound parts import from `c15t/react` as properties of `ConsentWidget`.
162
195
  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,20 @@ 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; set `iframeBlocker: false` to disable it, or
57
+ `iframeBlocker: { disableAutomaticBlocking: true }` to scan manually with
58
+ `useIframeBlocker({ disableAutomaticBlocking: true }).processAllIframes()`.
59
+
31
60
  Use effective permissions to gate work and explicit choices to inspect what the
32
61
  visitor confirmed. Read [consent state](../../../guides/consent-state.md) for that
33
62
  distinction and [migration](../../../upgrade-v3.md) for v3 policy configuration.
@@ -30,6 +30,18 @@ in your application while consent writes still go to Inth. Regular backend
30
30
  browser setup. See [data fetching and transports](./data-fetching.md) for
31
31
  the comparison, including custom transports and offline mode.
32
32
 
33
+ Manifest resolution removes the per-visitor `/init` request, and with it the
34
+ backend's only count of visitors. The server adapters replace it with a session
35
+ report: after each resolution, on a server-rendered page or the same-origin
36
+ init route, the host posts a small report to the backend's `POST /sessions`,
37
+ server-to-server and detached from the response. The browser makes no request
38
+ and the report stores no identity; the visitor's IP and user agent travel as
39
+ forwarded headers under the backend's usual IP handling. Each report is one
40
+ resolution; a page view can produce a `render` and a `route` report, and the
41
+ consuming side groups them into sessions by address and user agent within a
42
+ window. Static output resolves in the browser and sends none. Set `reportSessions: false` on an adapter to turn
43
+ it off.
44
+
33
45
  ## Match initialization to your application output
34
46
 
35
47
  | Application output | Initial state | Required setup |
@@ -31,6 +31,11 @@ Replace the example URL and implement the vendor's initialization. This is a
31
31
  loader template, not a functioning analytics SDK. The script stays blocked
32
32
  while measurement permission is denied.
33
33
 
34
+ Add `vendor: 'example-analytics'` and declare the vendor in the runtime's
35
+ `vendors` option or in the backend manifest when visitors should be able to
36
+ turn this vendor off inside a granted category. See
37
+ [granular consent](./granular-consent.md).
38
+
34
39
  ## Define revocation deliberately
35
40
 
36
41
  `onConsentChange` receives current permission information. Use it to update the