@c15t/svelte 3.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. package/AGENTS.md +88 -0
  2. package/README.md +136 -0
  3. package/SKILL.md +14 -0
  4. package/dist/actions/focus-trap.d.ts +8 -0
  5. package/dist/actions/focus-trap.js +23 -0
  6. package/dist/actions/portal.d.ts +8 -0
  7. package/dist/actions/portal.js +39 -0
  8. package/dist/actions/scroll-lock.d.ts +8 -0
  9. package/dist/actions/scroll-lock.js +23 -0
  10. package/dist/components/action-button.svelte +119 -0
  11. package/dist/components/action-button.svelte.d.ts +18 -0
  12. package/dist/components/branding.svelte +139 -0
  13. package/dist/components/branding.svelte.d.ts +13 -0
  14. package/dist/components/consent-dev-tools.svelte +58 -0
  15. package/dist/components/consent-dev-tools.svelte.d.ts +4 -0
  16. package/dist/components/frame.svelte +95 -0
  17. package/dist/components/frame.svelte.d.ts +12 -0
  18. package/dist/components/iab-panel.svelte +542 -0
  19. package/dist/components/iab-panel.svelte.d.ts +16 -0
  20. package/dist/components/iab-prompt.svelte +304 -0
  21. package/dist/components/iab-prompt.svelte.d.ts +14 -0
  22. package/dist/components/iab-purpose-item.svelte +511 -0
  23. package/dist/components/iab-purpose-item.svelte.d.ts +26 -0
  24. package/dist/components/iab-stack-item.svelte +146 -0
  25. package/dist/components/iab-stack-item.svelte.d.ts +19 -0
  26. package/dist/components/iab-vendor-list.svelte +900 -0
  27. package/dist/components/iab-vendor-list.svelte.d.ts +19 -0
  28. package/dist/components/icons/c15-t-icon-only.svelte +27 -0
  29. package/dist/components/icons/c15-t-icon-only.svelte.d.ts +9 -0
  30. package/dist/components/icons/chevron-down-icon.svelte +30 -0
  31. package/dist/components/icons/chevron-down-icon.svelte.d.ts +10 -0
  32. package/dist/components/icons/chevron-right-icon.svelte +36 -0
  33. package/dist/components/icons/chevron-right-icon.svelte.d.ts +12 -0
  34. package/dist/components/icons/close-icon.svelte +40 -0
  35. package/dist/components/icons/close-icon.svelte.d.ts +10 -0
  36. package/dist/components/icons/consent-icon-only.svelte +36 -0
  37. package/dist/components/icons/consent-icon-only.svelte.d.ts +9 -0
  38. package/dist/components/icons/external-link-icon.svelte +30 -0
  39. package/dist/components/icons/external-link-icon.svelte.d.ts +8 -0
  40. package/dist/components/icons/globe-icon.svelte +39 -0
  41. package/dist/components/icons/globe-icon.svelte.d.ts +9 -0
  42. package/dist/components/icons/info-icon.svelte +42 -0
  43. package/dist/components/icons/info-icon.svelte.d.ts +9 -0
  44. package/dist/components/icons/inth-logo.svelte +38 -0
  45. package/dist/components/icons/inth-logo.svelte.d.ts +9 -0
  46. package/dist/components/icons/layers-icon.svelte +25 -0
  47. package/dist/components/icons/layers-icon.svelte.d.ts +8 -0
  48. package/dist/components/icons/legitimate-interest-icon.svelte +29 -0
  49. package/dist/components/icons/legitimate-interest-icon.svelte.d.ts +10 -0
  50. package/dist/components/icons/lock-icon.svelte +31 -0
  51. package/dist/components/icons/lock-icon.svelte.d.ts +8 -0
  52. package/dist/components/icons/search-icon.svelte +33 -0
  53. package/dist/components/icons/search-icon.svelte.d.ts +8 -0
  54. package/dist/components/inline-legal-links.svelte +95 -0
  55. package/dist/components/inline-legal-links.svelte.d.ts +10 -0
  56. package/dist/components/manager-provider.svelte +459 -0
  57. package/dist/components/manager-provider.svelte.d.ts +27 -0
  58. package/dist/components/overlay.svelte +79 -0
  59. package/dist/components/overlay.svelte.d.ts +7 -0
  60. package/dist/components/panel-link.svelte +33 -0
  61. package/dist/components/panel-link.svelte.d.ts +10 -0
  62. package/dist/components/panel-trigger.svelte +237 -0
  63. package/dist/components/panel-trigger.svelte.d.ts +16 -0
  64. package/dist/components/panel.svelte +258 -0
  65. package/dist/components/panel.svelte.d.ts +22 -0
  66. package/dist/components/policy-actions-renderer.svelte +79 -0
  67. package/dist/components/policy-actions-renderer.svelte.d.ts +19 -0
  68. package/dist/components/preferences.svelte +320 -0
  69. package/dist/components/preferences.svelte.d.ts +8 -0
  70. package/dist/components/prompt.svelte +514 -0
  71. package/dist/components/prompt.svelte.d.ts +43 -0
  72. package/dist/context.svelte.d.ts +125 -0
  73. package/dist/context.svelte.js +354 -0
  74. package/dist/devtools-options.d.ts +7 -0
  75. package/dist/devtools-options.js +1 -0
  76. package/dist/devtools.d.ts +9 -0
  77. package/dist/devtools.js +7 -0
  78. package/dist/headless.d.ts +10 -0
  79. package/dist/headless.js +7 -0
  80. package/dist/iab/styles.css +11 -0
  81. package/dist/iab-loader.d.ts +30 -0
  82. package/dist/iab-loader.js +33 -0
  83. package/dist/iab-translations.d.ts +84 -0
  84. package/dist/iab-translations.js +45 -0
  85. package/dist/iab-types.d.ts +62 -0
  86. package/dist/iab-types.js +43 -0
  87. package/dist/index.d.ts +28 -0
  88. package/dist/index.js +21 -0
  89. package/dist/kit/app.d.ts +35 -0
  90. package/dist/kit/handle.d.ts +34 -0
  91. package/dist/kit/handle.js +87 -0
  92. package/dist/kit/index.d.ts +30 -0
  93. package/dist/kit/index.js +5 -0
  94. package/dist/kit/load-consent.d.ts +57 -0
  95. package/dist/kit/load-consent.js +144 -0
  96. package/dist/kit/routes.d.ts +46 -0
  97. package/dist/kit/routes.js +203 -0
  98. package/dist/kit/types.d.ts +56 -0
  99. package/dist/kit/types.js +1 -0
  100. package/dist/primitives/accordion/accordion-item-content.svelte +35 -0
  101. package/dist/primitives/accordion/accordion-item-content.svelte.d.ts +9 -0
  102. package/dist/primitives/accordion/accordion-item-trigger.svelte +54 -0
  103. package/dist/primitives/accordion/accordion-item-trigger.svelte.d.ts +10 -0
  104. package/dist/primitives/accordion/accordion-item.svelte +61 -0
  105. package/dist/primitives/accordion/accordion-item.svelte.d.ts +11 -0
  106. package/dist/primitives/accordion/accordion-root.svelte +61 -0
  107. package/dist/primitives/accordion/accordion-root.svelte.d.ts +13 -0
  108. package/dist/primitives/accordion/context.d.ts +18 -0
  109. package/dist/primitives/accordion/context.js +23 -0
  110. package/dist/primitives/accordion/index.d.ts +29 -0
  111. package/dist/primitives/accordion/index.js +11 -0
  112. package/dist/primitives/collapsible/collapsible-content.svelte +36 -0
  113. package/dist/primitives/collapsible/collapsible-content.svelte.d.ts +9 -0
  114. package/dist/primitives/collapsible/collapsible-indicator.svelte +29 -0
  115. package/dist/primitives/collapsible/collapsible-indicator.svelte.d.ts +9 -0
  116. package/dist/primitives/collapsible/collapsible-root.svelte +70 -0
  117. package/dist/primitives/collapsible/collapsible-root.svelte.d.ts +11 -0
  118. package/dist/primitives/collapsible/collapsible-trigger.svelte +52 -0
  119. package/dist/primitives/collapsible/collapsible-trigger.svelte.d.ts +10 -0
  120. package/dist/primitives/collapsible/context.d.ts +10 -0
  121. package/dist/primitives/collapsible/context.js +12 -0
  122. package/dist/primitives/collapsible/index.d.ts +26 -0
  123. package/dist/primitives/collapsible/index.js +11 -0
  124. package/dist/primitives/dialog/context.d.ts +14 -0
  125. package/dist/primitives/dialog/context.js +12 -0
  126. package/dist/primitives/dialog/dialog-backdrop.svelte +39 -0
  127. package/dist/primitives/dialog/dialog-backdrop.svelte.d.ts +7 -0
  128. package/dist/primitives/dialog/dialog-close-trigger.svelte +48 -0
  129. package/dist/primitives/dialog/dialog-close-trigger.svelte.d.ts +11 -0
  130. package/dist/primitives/dialog/dialog-content.svelte +81 -0
  131. package/dist/primitives/dialog/dialog-content.svelte.d.ts +9 -0
  132. package/dist/primitives/dialog/dialog-description.svelte +28 -0
  133. package/dist/primitives/dialog/dialog-description.svelte.d.ts +9 -0
  134. package/dist/primitives/dialog/dialog-positioner.svelte +33 -0
  135. package/dist/primitives/dialog/dialog-positioner.svelte.d.ts +9 -0
  136. package/dist/primitives/dialog/dialog-root.svelte +117 -0
  137. package/dist/primitives/dialog/dialog-root.svelte.d.ts +14 -0
  138. package/dist/primitives/dialog/dialog-title.svelte +28 -0
  139. package/dist/primitives/dialog/dialog-title.svelte.d.ts +9 -0
  140. package/dist/primitives/dialog/index.d.ts +50 -0
  141. package/dist/primitives/dialog/index.js +19 -0
  142. package/dist/primitives/index.d.ts +7 -0
  143. package/dist/primitives/index.js +7 -0
  144. package/dist/primitives/portal/index.d.ts +4 -0
  145. package/dist/primitives/portal/index.js +2 -0
  146. package/dist/primitives/portal/portal.svelte +23 -0
  147. package/dist/primitives/portal/portal.svelte.d.ts +9 -0
  148. package/dist/primitives/preference-item/context.d.ts +11 -0
  149. package/dist/primitives/preference-item/context.js +12 -0
  150. package/dist/primitives/preference-item/index.d.ts +54 -0
  151. package/dist/primitives/preference-item/index.js +21 -0
  152. package/dist/primitives/preference-item/preference-item-auxiliary.svelte +33 -0
  153. package/dist/primitives/preference-item/preference-item-auxiliary.svelte.d.ts +9 -0
  154. package/dist/primitives/preference-item/preference-item-content.svelte +68 -0
  155. package/dist/primitives/preference-item/preference-item-content.svelte.d.ts +14 -0
  156. package/dist/primitives/preference-item/preference-item-control.svelte +33 -0
  157. package/dist/primitives/preference-item/preference-item-control.svelte.d.ts +9 -0
  158. package/dist/primitives/preference-item/preference-item-header.svelte +33 -0
  159. package/dist/primitives/preference-item/preference-item-header.svelte.d.ts +9 -0
  160. package/dist/primitives/preference-item/preference-item-leading.svelte +33 -0
  161. package/dist/primitives/preference-item/preference-item-leading.svelte.d.ts +9 -0
  162. package/dist/primitives/preference-item/preference-item-meta.svelte +33 -0
  163. package/dist/primitives/preference-item/preference-item-meta.svelte.d.ts +9 -0
  164. package/dist/primitives/preference-item/preference-item-root.svelte +82 -0
  165. package/dist/primitives/preference-item/preference-item-root.svelte.d.ts +12 -0
  166. package/dist/primitives/preference-item/preference-item-title.svelte +33 -0
  167. package/dist/primitives/preference-item/preference-item-title.svelte.d.ts +9 -0
  168. package/dist/primitives/preference-item/preference-item-trigger.svelte +62 -0
  169. package/dist/primitives/preference-item/preference-item-trigger.svelte.d.ts +10 -0
  170. package/dist/primitives/switch/context.d.ts +7 -0
  171. package/dist/primitives/switch/context.js +12 -0
  172. package/dist/primitives/switch/index.d.ts +25 -0
  173. package/dist/primitives/switch/index.js +11 -0
  174. package/dist/primitives/switch/switch-control.svelte +21 -0
  175. package/dist/primitives/switch/switch-control.svelte.d.ts +9 -0
  176. package/dist/primitives/switch/switch-hidden-input.svelte +31 -0
  177. package/dist/primitives/switch/switch-hidden-input.svelte.d.ts +8 -0
  178. package/dist/primitives/switch/switch-root.svelte +86 -0
  179. package/dist/primitives/switch/switch-root.svelte.d.ts +12 -0
  180. package/dist/primitives/switch/switch-thumb.svelte +14 -0
  181. package/dist/primitives/switch/switch-thumb.svelte.d.ts +7 -0
  182. package/dist/primitives/tabs/context.d.ts +11 -0
  183. package/dist/primitives/tabs/context.js +12 -0
  184. package/dist/primitives/tabs/index.d.ts +32 -0
  185. package/dist/primitives/tabs/index.js +11 -0
  186. package/dist/primitives/tabs/tabs-content.svelte +45 -0
  187. package/dist/primitives/tabs/tabs-content.svelte.d.ts +11 -0
  188. package/dist/primitives/tabs/tabs-list.svelte +38 -0
  189. package/dist/primitives/tabs/tabs-list.svelte.d.ts +10 -0
  190. package/dist/primitives/tabs/tabs-root.svelte +67 -0
  191. package/dist/primitives/tabs/tabs-root.svelte.d.ts +14 -0
  192. package/dist/primitives/tabs/tabs-trigger.svelte +108 -0
  193. package/dist/primitives/tabs/tabs-trigger.svelte.d.ts +11 -0
  194. package/dist/server/headers.d.ts +13 -0
  195. package/dist/server/headers.js +37 -0
  196. package/dist/server/index.d.ts +8 -0
  197. package/dist/server/index.js +116 -0
  198. package/dist/server/normalize-url.d.ts +23 -0
  199. package/dist/server/normalize-url.js +46 -0
  200. package/dist/server/types.d.ts +25 -0
  201. package/dist/server/types.js +1 -0
  202. package/dist/styles.css +10 -0
  203. package/dist/transports/offline.d.ts +14 -0
  204. package/dist/transports/offline.js +22 -0
  205. package/dist/types.d.ts +70 -0
  206. package/dist/types.js +1 -0
  207. package/dist/use-banner-visibility.svelte.d.ts +25 -0
  208. package/dist/use-banner-visibility.svelte.js +108 -0
  209. package/dist/utils.d.ts +25 -0
  210. package/dist/utils.js +32 -0
  211. package/dist/version.d.ts +1 -0
  212. package/dist/version.js +2 -0
  213. package/docs/README.md +88 -0
  214. package/docs/assets/v3/brand-bar.png +0 -0
  215. package/docs/assets/v3/brand-card.png +0 -0
  216. package/docs/assets/v3/choice-wall.png +0 -0
  217. package/docs/assets/v3/mobile-card.png +0 -0
  218. package/docs/assets/v3/preferences.png +0 -0
  219. package/docs/customization/overview.md +45 -0
  220. package/docs/customization/recipes.md +79 -0
  221. package/docs/customization/slots.md +55 -0
  222. package/docs/customization/tokens.md +76 -0
  223. package/docs/customization/translations.md +49 -0
  224. package/docs/frameworks/svelte/quickstart.md +133 -0
  225. package/docs/frameworks/sveltekit/quickstart.md +144 -0
  226. package/docs/guides/consent-state.md +60 -0
  227. package/docs/guides/data-fetching.md +163 -0
  228. package/docs/guides/deployment-modes.md +63 -0
  229. package/docs/guides/troubleshooting.md +68 -0
  230. package/docs/guides/verify-consent.md +62 -0
  231. package/docs/integrations/adobe-analytics.md +294 -0
  232. package/docs/integrations/ahrefs-analytics.md +291 -0
  233. package/docs/integrations/amplitude.md +292 -0
  234. package/docs/integrations/building-integrations.md +47 -0
  235. package/docs/integrations/clearbit.md +291 -0
  236. package/docs/integrations/cloudflare-web-analytics.md +292 -0
  237. package/docs/integrations/crisp.md +294 -0
  238. package/docs/integrations/databuddy.md +311 -0
  239. package/docs/integrations/fathom-analytics.md +293 -0
  240. package/docs/integrations/google-maps.md +384 -0
  241. package/docs/integrations/google-tag-manager.md +306 -0
  242. package/docs/integrations/google-tag.md +305 -0
  243. package/docs/integrations/heap.md +292 -0
  244. package/docs/integrations/hightouch.md +293 -0
  245. package/docs/integrations/hotjar.md +292 -0
  246. package/docs/integrations/intercom.md +285 -0
  247. package/docs/integrations/linkedin-insights.md +291 -0
  248. package/docs/integrations/logrocket.md +293 -0
  249. package/docs/integrations/matomo-analytics.md +316 -0
  250. package/docs/integrations/meta-pixel.md +285 -0
  251. package/docs/integrations/microsoft-clarity.md +292 -0
  252. package/docs/integrations/microsoft-uet.md +291 -0
  253. package/docs/integrations/mixpanel-analytics.md +295 -0
  254. package/docs/integrations/openai-pixel.md +441 -0
  255. package/docs/integrations/overview.md +111 -0
  256. package/docs/integrations/pirsch.md +295 -0
  257. package/docs/integrations/plausible-analytics.md +295 -0
  258. package/docs/integrations/posthog.md +397 -0
  259. package/docs/integrations/promptwatch.md +291 -0
  260. package/docs/integrations/reddit-pixel.md +294 -0
  261. package/docs/integrations/rudderstack.md +328 -0
  262. package/docs/integrations/rybbit-analytics.md +296 -0
  263. package/docs/integrations/segment.md +292 -0
  264. package/docs/integrations/snapchat-pixel.md +293 -0
  265. package/docs/integrations/tiktok-pixel.md +291 -0
  266. package/docs/integrations/umami-analytics.md +295 -0
  267. package/docs/integrations/vercel-analytics.md +293 -0
  268. package/docs/integrations/x-pixel.md +291 -0
  269. package/docs/integrations/youtube.md +405 -0
  270. package/docs/upgrade-v3.md +334 -0
  271. package/iab/styles.css +1 -0
  272. package/package.json +153 -0
  273. package/readme.json +36 -0
  274. package/styles.css +1 -0
@@ -0,0 +1,511 @@
1
+ <script lang="ts">
2
+ import styles from '@c15t/ui/styles/components/iab-consent-dialog';
3
+ import switchStyles from '@c15t/ui/styles/components/switch';
4
+
5
+ import type { IABTranslations } from '../iab-translations';
6
+ import type { ProcessedPurpose, VendorId } from '../iab-types';
7
+ import { PreferenceItem, Switch } from '../primitives';
8
+ import ChevronRightIcon from './icons/chevron-right-icon.svelte';
9
+ import GlobeIcon from './icons/globe-icon.svelte';
10
+ import LegitimateInterestIcon from './icons/legitimate-interest-icon.svelte';
11
+ import LockIcon from './icons/lock-icon.svelte';
12
+
13
+ // The shared switch stylesheet, keyed off `data-size`, which is what
14
+ // the React and Vue switches use. The primitives sheet renders the same
15
+ // control through appended `root-small`/`track-small` classes; two
16
+ // sheets for one control is drift the IAB rows cannot afford.
17
+ const sw = switchStyles;
18
+
19
+ let {
20
+ purpose,
21
+ testId,
22
+ isEnabled,
23
+ onToggle,
24
+ vendorConsents,
25
+ onVendorToggle,
26
+ onVendorClick,
27
+ isLocked = false,
28
+ vendorLegitimateInterests = {},
29
+ onVendorLegitimateInterestToggle,
30
+ purposeLegitimateInterests = {},
31
+ onPurposeLegitimateInterestToggle,
32
+ noStyle = false,
33
+ iabT,
34
+ }: {
35
+ purpose: ProcessedPurpose;
36
+ /**
37
+ * The row's `data-testid`, from the shared display model. A purpose,
38
+ * a special purpose and a special feature can all be numbered `1`,
39
+ * so the id alone does not identify the row.
40
+ */
41
+ testId?: string;
42
+ isEnabled: boolean;
43
+ onToggle: (value: boolean) => void;
44
+ vendorConsents: Record<string, boolean>;
45
+ onVendorToggle: (vendorId: VendorId, value: boolean) => void;
46
+ onVendorClick: (vendorId: VendorId) => void;
47
+ isLocked?: boolean;
48
+ vendorLegitimateInterests?: Record<string, boolean>;
49
+ onVendorLegitimateInterestToggle?: (
50
+ vendorId: VendorId,
51
+ value: boolean
52
+ ) => void;
53
+ purposeLegitimateInterests?: Record<number, boolean>;
54
+ onPurposeLegitimateInterestToggle?: (
55
+ purposeId: number,
56
+ value: boolean
57
+ ) => void;
58
+ noStyle?: boolean;
59
+ iabT: IABTranslations;
60
+ } = $props();
61
+
62
+ let isExpanded = $state(false);
63
+ let showExamples = $state(false);
64
+ let showVendors = $state(false);
65
+ let purposeChecked = $state(false);
66
+
67
+ const legIntVendors = $derived(
68
+ purpose.vendors.filter((v) => v.usesLegitimateInterest)
69
+ );
70
+ const consentVendors = $derived(
71
+ purpose.vendors.filter((v) => !v.usesLegitimateInterest)
72
+ );
73
+
74
+ const getVendorConsent = function getVendorConsent(
75
+ vendorId: VendorId
76
+ ): boolean {
77
+ return vendorConsents[String(vendorId)] ?? false;
78
+ };
79
+
80
+ const getVendorLegitimateInterest = function getVendorLegitimateInterest(
81
+ vendorId: VendorId
82
+ ): boolean {
83
+ return vendorLegitimateInterests[String(vendorId)] ?? true;
84
+ };
85
+
86
+ // Check if purpose-level LI is allowed (not objected)
87
+ const isPurposeLIAllowed = $derived(
88
+ purposeLegitimateInterests[purpose.id] ?? true
89
+ );
90
+
91
+ // Handler for purpose-level LI objection
92
+ const handlePurposeLIObjection = function handlePurposeLIObjection() {
93
+ const newValue = !isPurposeLIAllowed;
94
+ onPurposeLegitimateInterestToggle?.(purpose.id, newValue);
95
+ if (onVendorLegitimateInterestToggle) {
96
+ for (const vendor of legIntVendors) {
97
+ onVendorLegitimateInterestToggle(vendor.id, newValue);
98
+ }
99
+ }
100
+ };
101
+
102
+ // Separate IAB and custom vendors
103
+ const iabConsentVendors = $derived(consentVendors.filter((v) => !v.isCustom));
104
+ const customConsentVendors = $derived(
105
+ consentVendors.filter((v) => v.isCustom)
106
+ );
107
+ const iabLegIntVendors = $derived(legIntVendors.filter((v) => !v.isCustom));
108
+ const customLegIntVendors = $derived(legIntVendors.filter((v) => v.isCustom));
109
+
110
+ $effect(() => {
111
+ purposeChecked = isEnabled;
112
+ });
113
+
114
+ // Handle purpose toggle - also toggles all consent-based vendors
115
+ const handlePurposeToggle = function handlePurposeToggle(value: boolean) {
116
+ onToggle(value);
117
+ for (const vendor of consentVendors) {
118
+ onVendorToggle(vendor.id, value);
119
+ }
120
+ };
121
+ </script>
122
+
123
+ <PreferenceItem.Root
124
+ bind:open={isExpanded}
125
+ class={noStyle ? '' : styles.purposeItem || ''}
126
+ data-testid={testId ?? `purpose-item-${purpose.id}`}
127
+ noStyle
128
+ >
129
+ <div class={noStyle ? '' : styles.purposeHeader || ''}>
130
+ <PreferenceItem.Trigger class={noStyle ? '' : styles.purposeTrigger || ''}>
131
+ <PreferenceItem.Leading>
132
+ <ChevronRightIcon
133
+ class={noStyle ? '' : styles.purposeArrow || ''}
134
+ expanded={isExpanded}
135
+ />
136
+ </PreferenceItem.Leading>
137
+ <PreferenceItem.Header class={noStyle ? '' : styles.purposeInfo || ''}>
138
+ <PreferenceItem.Title class={noStyle ? '' : styles.purposeName || ''}>
139
+ {purpose.name}
140
+ {#if isLocked}
141
+ <LockIcon class={noStyle ? '' : styles.lockIcon || ''} />
142
+ {/if}
143
+ </PreferenceItem.Title>
144
+ <PreferenceItem.Meta class={noStyle ? '' : styles.purposeMeta || ''}>
145
+ {iabT.preferenceCenter.purposeItem.partners.replace(
146
+ '{count}',
147
+ String(purpose.vendors.length)
148
+ )}
149
+ </PreferenceItem.Meta>
150
+ {#if legIntVendors.length > 0}
151
+ <PreferenceItem.Auxiliary
152
+ class={noStyle ? '' : styles.legitimateInterestBadge || ''}
153
+ >
154
+ <LegitimateInterestIcon
155
+ class={noStyle ? '' : styles.legitimateInterestIcon || ''}
156
+ />
157
+ {iabT.preferenceCenter.purposeItem.vendorsUseLegitimateInterest.replace(
158
+ '{count}',
159
+ String(legIntVendors.length)
160
+ )}
161
+ </PreferenceItem.Auxiliary>
162
+ {/if}
163
+ </PreferenceItem.Header>
164
+ </PreferenceItem.Trigger>
165
+ <PreferenceItem.Control>
166
+ <Switch.Root
167
+ aria-label={purpose.name}
168
+ bind:checked={purposeChecked}
169
+ onclick={() => handlePurposeToggle(purposeChecked)}
170
+ disabled={isLocked}
171
+ class={noStyle ? '' : sw.root}
172
+ data-size="medium"
173
+ >
174
+ <Switch.Control class={noStyle ? '' : sw.track}>
175
+ <Switch.Thumb class={noStyle ? '' : sw.thumb} />
176
+ </Switch.Control>
177
+ </Switch.Root>
178
+ </PreferenceItem.Control>
179
+ </div>
180
+
181
+ <!-- The surface's padding goes on the inner element: the outer one is
182
+ the collapsing grid, and padding there keeps a closed item open by
183
+ its own padding's worth. -->
184
+ <PreferenceItem.Content
185
+ innerClassName={noStyle ? '' : styles.purposeContent || ''}
186
+ >
187
+ <p class={noStyle ? '' : styles.purposeDescription || ''}>
188
+ {purpose.description}
189
+ </p>
190
+
191
+ <!-- Purpose-level Legitimate Interest Objection -->
192
+ {#if legIntVendors.length > 0 && onPurposeLegitimateInterestToggle}
193
+ <div class={noStyle ? '' : styles.purposeLiSection || ''}>
194
+ <div class={noStyle ? '' : styles.purposeLiSectionHeader || ''}>
195
+ <div class={noStyle ? '' : styles.purposeLiInfo || ''}>
196
+ <LegitimateInterestIcon
197
+ class={noStyle ? '' : styles.legitimateInterestIcon || ''}
198
+ />
199
+ <span>
200
+ {iabT.preferenceCenter.purposeItem.vendorsUseLegitimateInterest.replace(
201
+ '{count}',
202
+ String(legIntVendors.length)
203
+ )}
204
+ </span>
205
+ </div>
206
+ <button
207
+ type="button"
208
+ onclick={handlePurposeLIObjection}
209
+ class={noStyle
210
+ ? ''
211
+ : `${styles.objectButton || ''} ${!isPurposeLIAllowed ? styles.objectButtonActive || '' : ''}`}
212
+ aria-pressed={!isPurposeLIAllowed}
213
+ >
214
+ {isPurposeLIAllowed
215
+ ? iabT.preferenceCenter.purposeItem.objectButton
216
+ : iabT.preferenceCenter.purposeItem.objected}
217
+ </button>
218
+ </div>
219
+ <p class={noStyle ? '' : styles.liExplanation || ''}>
220
+ {iabT.preferenceCenter.purposeItem.rightToObject}
221
+ </p>
222
+ </div>
223
+ {/if}
224
+
225
+ <!-- Legacy badge when no toggle handler -->
226
+ {#if legIntVendors.length > 0 && !onPurposeLegitimateInterestToggle}
227
+ <div class={noStyle ? '' : styles.legitimateInterestBadge || ''}>
228
+ <LegitimateInterestIcon
229
+ class={noStyle ? '' : styles.legitimateInterestIcon || ''}
230
+ />
231
+ {iabT.preferenceCenter.purposeItem.vendorsUseLegitimateInterest.replace(
232
+ '{count}',
233
+ String(legIntVendors.length)
234
+ )}
235
+ </div>
236
+ {/if}
237
+
238
+ <!-- Illustrations / Examples -->
239
+ {#if purpose.illustrations && purpose.illustrations.length > 0}
240
+ <div>
241
+ <PreferenceItem.Root
242
+ bind:open={showExamples}
243
+ noStyle
244
+ >
245
+ <PreferenceItem.Trigger
246
+ class={noStyle ? '' : styles.examplesToggle || ''}
247
+ >
248
+ <ChevronRightIcon
249
+ style="height:0.75rem;width:0.75rem"
250
+ expanded={showExamples}
251
+ />
252
+ {iabT.preferenceCenter.purposeItem.examples}
253
+ ({purpose.illustrations.length})
254
+ </PreferenceItem.Trigger>
255
+ <PreferenceItem.Content>
256
+ <ul class={noStyle ? '' : styles.examplesList || ''}>
257
+ {#each purpose.illustrations as illustration (illustration)}
258
+ <li>{illustration}</li>
259
+ {/each}
260
+ </ul>
261
+ </PreferenceItem.Content>
262
+ </PreferenceItem.Root>
263
+ </div>
264
+ {/if}
265
+
266
+ <!-- Vendor list within purpose -->
267
+ <div>
268
+ <PreferenceItem.Root
269
+ bind:open={showVendors}
270
+ noStyle
271
+ >
272
+ <PreferenceItem.Trigger
273
+ class={noStyle ? '' : styles.vendorsToggle || ''}
274
+ >
275
+ <ChevronRightIcon
276
+ style="height:0.75rem;width:0.75rem"
277
+ expanded={showVendors}
278
+ />
279
+ {iabT.preferenceCenter.purposeItem.partnersUsingPurpose}
280
+ ({purpose.vendors.length})
281
+ </PreferenceItem.Trigger>
282
+ <PreferenceItem.Content
283
+ innerClassName={noStyle ? '' : styles.vendorSection || ''}
284
+ >
285
+ <!-- IAB Consent Vendors -->
286
+ {#if iabConsentVendors.length > 0}
287
+ <h5 class={noStyle ? '' : styles.vendorSectionTitle || ''}>
288
+ {iabT.preferenceCenter.purposeItem.withYourPermission}
289
+ ({iabConsentVendors.length})
290
+ </h5>
291
+ {#each iabConsentVendors as vendor (vendor.id)}
292
+ {@const isConsented = getVendorConsent(vendor.id)}
293
+ <div class={noStyle ? '' : styles.vendorRow || ''}>
294
+ <div class={noStyle ? '' : styles.vendorInfo || ''}>
295
+ <button
296
+ type="button"
297
+ onclick={() => onVendorClick(vendor.id)}
298
+ class={noStyle ? '' : styles.vendorName || ''}
299
+ >
300
+ <span>{vendor.name}</span>
301
+ </button>
302
+ <div class={noStyle ? '' : styles.vendorDetails || ''}>
303
+ {#if vendor.usesCookies}
304
+ <span class={noStyle ? '' : styles.vendorDetail || ''}>
305
+ {iabT.preferenceCenter.vendorList.usesCookies}
306
+ </span>
307
+ {/if}
308
+ {#if vendor.usesNonCookieAccess}
309
+ <span class={noStyle ? '' : styles.vendorDetail || ''}>
310
+ {iabT.preferenceCenter.vendorList.nonCookieAccess}
311
+ </span>
312
+ {/if}
313
+ </div>
314
+ </div>
315
+ <Switch.Root
316
+ aria-label={vendor.name}
317
+ checked={isConsented}
318
+ onclick={() => onVendorToggle(vendor.id, !isConsented)}
319
+ class={noStyle ? '' : sw.root}
320
+ data-size="small"
321
+ >
322
+ <Switch.Control class={noStyle ? '' : sw.track}>
323
+ <Switch.Thumb class={noStyle ? '' : sw.thumb} />
324
+ </Switch.Control>
325
+ </Switch.Root>
326
+ </div>
327
+ {/each}
328
+ {/if}
329
+
330
+ <!-- IAB Legitimate Interest Vendors -->
331
+ {#if iabLegIntVendors.length > 0}
332
+ <h5
333
+ class={noStyle
334
+ ? ''
335
+ : `${styles.vendorSectionTitle || ''} ${styles.vendorSectionTitleLi || ''}`}
336
+ >
337
+ <LegitimateInterestIcon
338
+ class={noStyle ? '' : styles.legitimateInterestIcon || ''}
339
+ />
340
+ {iabT.preferenceCenter.purposeItem.legitimateInterest}
341
+ ({iabLegIntVendors.length})
342
+ </h5>
343
+ <p class={noStyle ? '' : styles.liExplanation || ''}>
344
+ {iabT.preferenceCenter.purposeItem.rightToObject}
345
+ </p>
346
+ {#each iabLegIntVendors as vendor (vendor.id)}
347
+ {@const isConsented = getVendorConsent(vendor.id)}
348
+ {@const isLIAllowed = getVendorLegitimateInterest(vendor.id)}
349
+ {@const showLIControl = !!onVendorLegitimateInterestToggle}
350
+ <div
351
+ class={noStyle
352
+ ? ''
353
+ : `${styles.vendorRow || ''} ${styles.vendorRowLi || ''}`}
354
+ >
355
+ <div class={noStyle ? '' : styles.vendorInfo || ''}>
356
+ <button
357
+ type="button"
358
+ onclick={() => onVendorClick(vendor.id)}
359
+ class={noStyle ? '' : styles.vendorName || ''}
360
+ >
361
+ <span>{vendor.name}</span>
362
+ </button>
363
+ <div class={noStyle ? '' : styles.vendorDetails || ''}>
364
+ <span
365
+ class={noStyle
366
+ ? ''
367
+ : `${styles.vendorDetail || ''} ${styles.vendorDetailLi || ''}`}
368
+ >
369
+ {iabT.preferenceCenter.purposeItem.legitimateInterest}
370
+ </span>
371
+ {#if vendor.usesCookies}
372
+ <span class={noStyle ? '' : styles.vendorDetail || ''}>
373
+ {iabT.preferenceCenter.vendorList.usesCookies}
374
+ </span>
375
+ {/if}
376
+ </div>
377
+ </div>
378
+ {#if showLIControl}
379
+ <button
380
+ type="button"
381
+ onclick={() =>
382
+ onVendorLegitimateInterestToggle?.(
383
+ vendor.id,
384
+ !isLIAllowed
385
+ )}
386
+ class={noStyle
387
+ ? ''
388
+ : `${styles.objectButton || ''} ${!isLIAllowed ? styles.objectButtonActive || '' : ''}`}
389
+ aria-pressed={!isLIAllowed}
390
+ >
391
+ {isLIAllowed
392
+ ? iabT.preferenceCenter.purposeItem.objectButton
393
+ : iabT.preferenceCenter.purposeItem.objected}
394
+ </button>
395
+ {:else}
396
+ <Switch.Root
397
+ aria-label={vendor.name}
398
+ checked={isConsented}
399
+ onclick={() => onVendorToggle(vendor.id, !isConsented)}
400
+ class={noStyle ? '' : sw.root}
401
+ data-size="small"
402
+ >
403
+ <Switch.Control class={noStyle ? '' : sw.track}>
404
+ <Switch.Thumb class={noStyle ? '' : sw.thumb} />
405
+ </Switch.Control>
406
+ </Switch.Root>
407
+ {/if}
408
+ </div>
409
+ {/each}
410
+ {/if}
411
+
412
+ <!-- Custom Vendors -->
413
+ {#if customConsentVendors.length > 0 || customLegIntVendors.length > 0}
414
+ <div class={noStyle ? '' : styles.customVendorPurposeSection || ''}>
415
+ <h5 class={noStyle ? '' : styles.vendorSectionTitleCustom || ''}>
416
+ <GlobeIcon
417
+ class={noStyle ? '' : styles.legitimateInterestIcon || ''}
418
+ />
419
+ {iabT.preferenceCenter.vendorList.customVendorsHeading}
420
+ ({customConsentVendors.length + customLegIntVendors.length})
421
+ </h5>
422
+ {#each customConsentVendors as vendor (vendor.id)}
423
+ {@const isConsented = getVendorConsent(vendor.id)}
424
+ <div class={noStyle ? '' : styles.vendorRow || ''}>
425
+ <div class={noStyle ? '' : styles.vendorInfo || ''}>
426
+ <button
427
+ type="button"
428
+ onclick={() => onVendorClick(vendor.id)}
429
+ class={noStyle ? '' : styles.vendorName || ''}
430
+ >
431
+ <span>{vendor.name}</span>
432
+ <GlobeIcon
433
+ class={noStyle ? '' : styles.customVendorIcon || ''}
434
+ aria-label={iabT.common.customPartner}
435
+ />
436
+ </button>
437
+ </div>
438
+ <Switch.Root
439
+ aria-label={vendor.name}
440
+ checked={isConsented}
441
+ onclick={() => onVendorToggle(vendor.id, !isConsented)}
442
+ class={noStyle ? '' : sw.root}
443
+ data-size="small"
444
+ >
445
+ <Switch.Control class={noStyle ? '' : sw.track}>
446
+ <Switch.Thumb class={noStyle ? '' : sw.thumb} />
447
+ </Switch.Control>
448
+ </Switch.Root>
449
+ </div>
450
+ {/each}
451
+ {#each customLegIntVendors as vendor (vendor.id)}
452
+ {@const isConsented = getVendorConsent(vendor.id)}
453
+ {@const isLIAllowed = getVendorLegitimateInterest(vendor.id)}
454
+ {@const showLIControl = !!onVendorLegitimateInterestToggle}
455
+ <div
456
+ class={noStyle
457
+ ? ''
458
+ : `${styles.vendorRow || ''} ${styles.vendorRowLi || ''}`}
459
+ >
460
+ <div class={noStyle ? '' : styles.vendorInfo || ''}>
461
+ <button
462
+ type="button"
463
+ onclick={() => onVendorClick(vendor.id)}
464
+ class={noStyle ? '' : styles.vendorName || ''}
465
+ >
466
+ <span>{vendor.name}</span>
467
+ <GlobeIcon
468
+ class={noStyle ? '' : styles.customVendorIcon || ''}
469
+ aria-label={iabT.common.customPartner}
470
+ />
471
+ </button>
472
+ </div>
473
+ {#if showLIControl}
474
+ <button
475
+ type="button"
476
+ onclick={() =>
477
+ onVendorLegitimateInterestToggle?.(
478
+ vendor.id,
479
+ !isLIAllowed
480
+ )}
481
+ class={noStyle
482
+ ? ''
483
+ : `${styles.objectButton || ''} ${!isLIAllowed ? styles.objectButtonActive || '' : ''}`}
484
+ aria-pressed={!isLIAllowed}
485
+ >
486
+ {isLIAllowed
487
+ ? iabT.preferenceCenter.purposeItem.objectButton
488
+ : iabT.preferenceCenter.purposeItem.objected}
489
+ </button>
490
+ {:else}
491
+ <Switch.Root
492
+ aria-label={vendor.name}
493
+ checked={isConsented}
494
+ onclick={() => onVendorToggle(vendor.id, !isConsented)}
495
+ class={noStyle ? '' : sw.root}
496
+ data-size="small"
497
+ >
498
+ <Switch.Control class={noStyle ? '' : sw.track}>
499
+ <Switch.Thumb class={noStyle ? '' : sw.thumb} />
500
+ </Switch.Control>
501
+ </Switch.Root>
502
+ {/if}
503
+ </div>
504
+ {/each}
505
+ </div>
506
+ {/if}
507
+ </PreferenceItem.Content>
508
+ </PreferenceItem.Root>
509
+ </div>
510
+ </PreferenceItem.Content>
511
+ </PreferenceItem.Root>
@@ -0,0 +1,26 @@
1
+ import type { IABTranslations } from '../iab-translations';
2
+ import type { ProcessedPurpose, VendorId } from '../iab-types';
3
+ type $$ComponentProps = {
4
+ purpose: ProcessedPurpose;
5
+ /**
6
+ * The row's `data-testid`, from the shared display model. A purpose,
7
+ * a special purpose and a special feature can all be numbered `1`,
8
+ * so the id alone does not identify the row.
9
+ */
10
+ testId?: string;
11
+ isEnabled: boolean;
12
+ onToggle: (value: boolean) => void;
13
+ vendorConsents: Record<string, boolean>;
14
+ onVendorToggle: (vendorId: VendorId, value: boolean) => void;
15
+ onVendorClick: (vendorId: VendorId) => void;
16
+ isLocked?: boolean;
17
+ vendorLegitimateInterests?: Record<string, boolean>;
18
+ onVendorLegitimateInterestToggle?: (vendorId: VendorId, value: boolean) => void;
19
+ purposeLegitimateInterests?: Record<number, boolean>;
20
+ onPurposeLegitimateInterestToggle?: (purposeId: number, value: boolean) => void;
21
+ noStyle?: boolean;
22
+ iabT: IABTranslations;
23
+ };
24
+ declare const IabPurposeItem: import("svelte").Component<$$ComponentProps, {}, "">;
25
+ type IabPurposeItem = ReturnType<typeof IabPurposeItem>;
26
+ export default IabPurposeItem;
@@ -0,0 +1,146 @@
1
+ <script lang="ts">
2
+ import styles from '@c15t/ui/styles/components/iab-consent-dialog';
3
+ import switchStyles from '@c15t/ui/styles/components/switch';
4
+
5
+ import type { IABTranslations } from '../iab-translations';
6
+ import type { ProcessedStack, VendorId } from '../iab-types';
7
+ import { PreferenceItem, Switch } from '../primitives';
8
+ import IABPurposeItem from './iab-purpose-item.svelte';
9
+ import ChevronRightIcon from './icons/chevron-right-icon.svelte';
10
+
11
+ const sw = switchStyles;
12
+
13
+ let {
14
+ stack,
15
+ consents,
16
+ onToggle,
17
+ vendorConsents,
18
+ onVendorToggle,
19
+ onVendorClick,
20
+ vendorLegitimateInterests = {},
21
+ onVendorLegitimateInterestToggle,
22
+ purposeLegitimateInterests = {},
23
+ onPurposeLegitimateInterestToggle,
24
+ noStyle = false,
25
+ iabT,
26
+ }: {
27
+ stack: ProcessedStack;
28
+ consents: Record<number, boolean>;
29
+ onToggle: (purposeId: number, value: boolean) => void;
30
+ vendorConsents: Record<string, boolean>;
31
+ onVendorToggle: (vendorId: VendorId, value: boolean) => void;
32
+ onVendorClick: (vendorId: VendorId) => void;
33
+ vendorLegitimateInterests?: Record<string, boolean>;
34
+ onVendorLegitimateInterestToggle?: (
35
+ vendorId: VendorId,
36
+ value: boolean
37
+ ) => void;
38
+ purposeLegitimateInterests?: Record<number, boolean>;
39
+ onPurposeLegitimateInterestToggle?: (
40
+ purposeId: number,
41
+ value: boolean
42
+ ) => void;
43
+ noStyle?: boolean;
44
+ iabT: IABTranslations;
45
+ } = $props();
46
+
47
+ let isExpanded = $state(false);
48
+ let stackChecked = $state(false);
49
+
50
+ const allEnabled = $derived(
51
+ stack.purposes.every((p) => consents[p.id] ?? false)
52
+ );
53
+ const someEnabled = $derived(
54
+ stack.purposes.some((p) => consents[p.id] ?? false) && !allEnabled
55
+ );
56
+
57
+ $effect(() => {
58
+ stackChecked = allEnabled;
59
+ });
60
+
61
+ const handleStackToggle = function handleStackToggle(value: boolean) {
62
+ for (const purpose of stack.purposes) {
63
+ onToggle(purpose.id, value);
64
+ for (const vendor of purpose.vendors) {
65
+ if (!vendor.usesLegitimateInterest) {
66
+ onVendorToggle(vendor.id, value);
67
+ }
68
+ }
69
+ }
70
+ };
71
+
72
+ const totalVendors = $derived(
73
+ new Set(stack.purposes.flatMap((p) => p.vendors.map((v) => v.id))).size
74
+ );
75
+ </script>
76
+
77
+ <PreferenceItem.Root
78
+ bind:open={isExpanded}
79
+ class={noStyle ? '' : styles.stackItem || ''}
80
+ data-testid={`stack-item-${stack.id}`}
81
+ noStyle
82
+ >
83
+ <div class={noStyle ? '' : styles.stackHeader || ''}>
84
+ <PreferenceItem.Trigger class={noStyle ? '' : styles.stackTrigger || ''}>
85
+ <PreferenceItem.Leading>
86
+ <ChevronRightIcon
87
+ class={noStyle ? '' : styles.purposeArrow || ''}
88
+ expanded={isExpanded}
89
+ />
90
+ </PreferenceItem.Leading>
91
+ <PreferenceItem.Header class={noStyle ? '' : styles.stackInfo || ''}>
92
+ <PreferenceItem.Title class={noStyle ? '' : styles.stackName || ''}>
93
+ {stack.name}
94
+ </PreferenceItem.Title>
95
+ <PreferenceItem.Meta class={noStyle ? '' : styles.stackMeta || ''}>
96
+ {totalVendors}
97
+ {totalVendors === 1
98
+ ? iabT.preferenceCenter.vendorList.partnerSingular
99
+ : iabT.preferenceCenter.vendorList.partnerPlural}
100
+ </PreferenceItem.Meta>
101
+ </PreferenceItem.Header>
102
+ </PreferenceItem.Trigger>
103
+ <PreferenceItem.Control class={noStyle ? '' : styles.stackControls || ''}>
104
+ {#if someEnabled}
105
+ <span class={noStyle ? '' : styles.srOnly || ''}>Partially enabled</span
106
+ >
107
+ <div class={noStyle ? '' : styles.partialIndicator || ''}></div>
108
+ {/if}
109
+ <Switch.Root
110
+ aria-label={stack.name}
111
+ bind:checked={stackChecked}
112
+ onclick={() => handleStackToggle(stackChecked)}
113
+ class={noStyle ? '' : sw.root}
114
+ data-size="medium"
115
+ >
116
+ <Switch.Control class={noStyle ? '' : sw.track}>
117
+ <Switch.Thumb class={noStyle ? '' : sw.thumb} />
118
+ </Switch.Control>
119
+ </Switch.Root>
120
+ </PreferenceItem.Control>
121
+ </div>
122
+
123
+ <PreferenceItem.Content>
124
+ <div class={noStyle ? '' : styles.stackDescription || ''}>
125
+ <p>{stack.description}</p>
126
+ </div>
127
+ <div class={noStyle ? '' : styles.stackContent || ''}>
128
+ {#each stack.purposes as purpose (purpose.id)}
129
+ <IABPurposeItem
130
+ {purpose}
131
+ isEnabled={consents[purpose.id] ?? false}
132
+ onToggle={(value) => onToggle(purpose.id, value)}
133
+ {vendorConsents}
134
+ {onVendorToggle}
135
+ {onVendorClick}
136
+ {vendorLegitimateInterests}
137
+ {onVendorLegitimateInterestToggle}
138
+ {purposeLegitimateInterests}
139
+ {onPurposeLegitimateInterestToggle}
140
+ {noStyle}
141
+ {iabT}
142
+ />
143
+ {/each}
144
+ </div>
145
+ </PreferenceItem.Content>
146
+ </PreferenceItem.Root>
@@ -0,0 +1,19 @@
1
+ import type { IABTranslations } from '../iab-translations';
2
+ import type { ProcessedStack, VendorId } from '../iab-types';
3
+ type $$ComponentProps = {
4
+ stack: ProcessedStack;
5
+ consents: Record<number, boolean>;
6
+ onToggle: (purposeId: number, value: boolean) => void;
7
+ vendorConsents: Record<string, boolean>;
8
+ onVendorToggle: (vendorId: VendorId, value: boolean) => void;
9
+ onVendorClick: (vendorId: VendorId) => void;
10
+ vendorLegitimateInterests?: Record<string, boolean>;
11
+ onVendorLegitimateInterestToggle?: (vendorId: VendorId, value: boolean) => void;
12
+ purposeLegitimateInterests?: Record<number, boolean>;
13
+ onPurposeLegitimateInterestToggle?: (purposeId: number, value: boolean) => void;
14
+ noStyle?: boolean;
15
+ iabT: IABTranslations;
16
+ };
17
+ declare const IabStackItem: import("svelte").Component<$$ComponentProps, {}, "">;
18
+ type IabStackItem = ReturnType<typeof IabStackItem>;
19
+ export default IabStackItem;