@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,900 @@
1
+ <script lang="ts">
2
+ import type { GlobalVendorList } from '@c15t/core';
3
+ import styles from '@c15t/ui/styles/components/iab-consent-dialog';
4
+ import switchStyles from '@c15t/ui/styles/components/switch';
5
+ import { switchVariants } from '@c15t/ui/styles/primitives';
6
+ import { untrack } from 'svelte';
7
+ import { SvelteMap, SvelteSet } from 'svelte/reactivity';
8
+
9
+ import type { IABTranslations } from '../iab-translations';
10
+ import type {
11
+ NonIABVendor,
12
+ ProcessedPurpose,
13
+ ProcessedVendor,
14
+ VendorId,
15
+ } from '../iab-types';
16
+ import { PreferenceItem, Switch } from '../primitives';
17
+ import ChevronDownIcon from './icons/chevron-down-icon.svelte';
18
+ import CloseIcon from './icons/close-icon.svelte';
19
+ import ExternalLinkIcon from './icons/external-link-icon.svelte';
20
+ import GlobeIcon from './icons/globe-icon.svelte';
21
+ import LayersIcon from './icons/layers-icon.svelte';
22
+ import LegitimateInterestIcon from './icons/legitimate-interest-icon.svelte';
23
+ import LockIcon from './icons/lock-icon.svelte';
24
+ import SearchIcon from './icons/search-icon.svelte';
25
+
26
+ const sw = switchVariants();
27
+
28
+ let {
29
+ vendorData,
30
+ purposes,
31
+ vendorConsents,
32
+ onVendorToggle,
33
+ selectedVendorId,
34
+ onClearSelection,
35
+ customVendors = [],
36
+ vendorLegitimateInterests = {},
37
+ onVendorLegitimateInterestToggle,
38
+ noStyle = false,
39
+ iabT,
40
+ }: {
41
+ vendorData: GlobalVendorList | null;
42
+ purposes: ProcessedPurpose[];
43
+ vendorConsents: Record<string, boolean>;
44
+ onVendorToggle: (vendorId: VendorId, value: boolean) => void;
45
+ selectedVendorId: VendorId | null;
46
+ onClearSelection: () => void;
47
+ customVendors?: NonIABVendor[];
48
+ vendorLegitimateInterests?: Record<string, boolean>;
49
+ onVendorLegitimateInterestToggle?: (
50
+ vendorId: VendorId,
51
+ value: boolean
52
+ ) => void;
53
+ noStyle?: boolean;
54
+ iabT: IABTranslations;
55
+ } = $props();
56
+
57
+ let searchTerm = $state('');
58
+ let expandedVendors = new SvelteSet<VendorId>();
59
+
60
+ // Map IAB vendors
61
+ const iabVendors = $derived.by((): ProcessedVendor[] => {
62
+ if (!vendorData) {
63
+ return [];
64
+ }
65
+ return Object.entries(vendorData.vendors).map(([id, vendor]) => ({
66
+ cookieMaxAgeSeconds: vendor.cookieMaxAgeSeconds,
67
+ cookieRefresh: vendor.cookieRefresh,
68
+ dataDeclaration:
69
+ (vendor as unknown as { dataDeclaration?: number[] }).dataDeclaration ||
70
+ [],
71
+ dataRetention: vendor.dataRetention,
72
+ deviceStorageDisclosureUrl: vendor.deviceStorageDisclosureUrl ?? null,
73
+ features: vendor.features || [],
74
+ id: Number(id),
75
+ isCustom: false,
76
+ legIntPurposes: vendor.legIntPurposes || [],
77
+ legitimateInterestUrl:
78
+ vendor.urls?.find((url) => url.legIntClaim)?.legIntClaim ?? null,
79
+ name: vendor.name,
80
+ policyUrl: (vendor as unknown as { policyUrl?: string }).policyUrl ?? '',
81
+ purposes: vendor.purposes || [],
82
+ specialFeatures: vendor.specialFeatures || [],
83
+ specialPurposes: vendor.specialPurposes || [],
84
+ usesCookies: vendor.usesCookies,
85
+ usesNonCookieAccess: vendor.usesNonCookieAccess,
86
+ }));
87
+ });
88
+
89
+ // Map custom vendors
90
+ const mappedCustomVendors = $derived.by((): ProcessedVendor[] =>
91
+ customVendors.map((cv) => ({
92
+ cookieMaxAgeSeconds: cv.cookieMaxAgeSeconds ?? null,
93
+ cookieRefresh: undefined,
94
+ dataDeclaration: cv.dataCategories || [],
95
+ dataRetention: undefined,
96
+ deviceStorageDisclosureUrl: null,
97
+ features: cv.features || [],
98
+ id: cv.id,
99
+ isCustom: true,
100
+ legIntPurposes: cv.legIntPurposes || [],
101
+ legitimateInterestUrl: null,
102
+ name: cv.name,
103
+ policyUrl: cv.privacyPolicyUrl,
104
+ purposes: cv.purposes || [],
105
+ specialFeatures: cv.specialFeatures || [],
106
+ specialPurposes: [],
107
+ usesCookies: cv.usesCookies ?? false,
108
+ usesNonCookieAccess: cv.usesNonCookieAccess ?? false,
109
+ }))
110
+ );
111
+
112
+ // Combine and sort all vendors
113
+ const vendors = $derived(
114
+ [...iabVendors, ...mappedCustomVendors].sort((a, b) =>
115
+ a.name.localeCompare(b.name)
116
+ )
117
+ );
118
+
119
+ // Scroll to selected vendor.
120
+ // Uses untrack for expandedVendors write to avoid reactive cycle,
121
+ // and cleans up the timeout if selectedVendorId changes rapidly.
122
+ $effect(() => {
123
+ if (selectedVendorId !== null) {
124
+ const id = selectedVendorId;
125
+ untrack(() => {
126
+ expandedVendors.add(id);
127
+ });
128
+ const timer = setTimeout(() => {
129
+ const element = document.getElementById(`vendor-${String(id)}`);
130
+ element?.scrollIntoView({ behavior: 'smooth', block: 'start' });
131
+ }, 100);
132
+ return () => clearTimeout(timer);
133
+ }
134
+ });
135
+
136
+ const filteredVendors = $derived.by(() => {
137
+ if (selectedVendorId !== null) {
138
+ return vendors.filter((v) => String(v.id) === String(selectedVendorId));
139
+ }
140
+ return vendors.filter((vendor) =>
141
+ vendor.name.toLowerCase().includes(searchTerm.toLowerCase())
142
+ );
143
+ });
144
+
145
+ const filteredIABVendors = $derived(
146
+ filteredVendors.filter((v) => !v.isCustom)
147
+ );
148
+ const filteredCustomVendors = $derived(
149
+ filteredVendors.filter((v) => v.isCustom)
150
+ );
151
+
152
+ const handleVendorOpenChange = function handleVendorOpenChange(
153
+ vendorId: VendorId,
154
+ open: boolean
155
+ ) {
156
+ if (open) {
157
+ expandedVendors.add(vendorId);
158
+ } else {
159
+ expandedVendors.delete(vendorId);
160
+ }
161
+ };
162
+
163
+ // Precompute vendor lookup maps once (O(V+P)) instead of per-vendor-per-render (O(V×P)).
164
+ type VendorPurposeEntry = ProcessedPurpose & {
165
+ usesLegitimateInterest: boolean;
166
+ };
167
+ interface SimpleEntry {
168
+ id: number;
169
+ name: string;
170
+ description: string;
171
+ }
172
+
173
+ const vendorPurposesMap = $derived.by(() => {
174
+ const map = new SvelteMap<string, VendorPurposeEntry[]>();
175
+ for (const vendor of vendors) {
176
+ const key = String(vendor.id);
177
+ const matched = purposes
178
+ .filter((purpose) => purpose.vendors.some((v) => String(v.id) === key))
179
+ .map((purpose) => ({
180
+ ...purpose,
181
+ usesLegitimateInterest: vendor.legIntPurposes.includes(purpose.id),
182
+ }));
183
+ map.set(key, matched);
184
+ }
185
+ return map;
186
+ });
187
+
188
+ const vendorSpecialPurposesMap = $derived.by(() => {
189
+ const map = new SvelteMap<string, SimpleEntry[]>();
190
+ if (!vendorData) {
191
+ return map;
192
+ }
193
+ for (const vendor of vendors) {
194
+ const entries = vendor.specialPurposes
195
+ .map((id) => vendorData.specialPurposes[id])
196
+ .filter(
197
+ (sp): sp is NonNullable<typeof sp> => sp !== null && sp !== undefined
198
+ )
199
+ .map((sp) => ({
200
+ description: sp.description,
201
+ id: sp.id,
202
+ name: sp.name,
203
+ }));
204
+ map.set(String(vendor.id), entries);
205
+ }
206
+ return map;
207
+ });
208
+
209
+ const vendorSpecialFeaturesMap = $derived.by(() => {
210
+ const map = new SvelteMap<string, SimpleEntry[]>();
211
+ if (!vendorData) {
212
+ return map;
213
+ }
214
+ for (const vendor of vendors) {
215
+ const entries = vendor.specialFeatures
216
+ .map((id) => vendorData.specialFeatures[id])
217
+ .filter(
218
+ (sf): sf is NonNullable<typeof sf> => sf !== null && sf !== undefined
219
+ )
220
+ .map((sf) => ({
221
+ description: sf.description,
222
+ id: sf.id,
223
+ name: sf.name,
224
+ }));
225
+ map.set(String(vendor.id), entries);
226
+ }
227
+ return map;
228
+ });
229
+
230
+ const vendorFeaturesMap = $derived.by(() => {
231
+ const map = new SvelteMap<string, SimpleEntry[]>();
232
+ if (!vendorData) {
233
+ return map;
234
+ }
235
+ for (const vendor of vendors) {
236
+ const entries = (vendor.features || [])
237
+ .map((id) => vendorData.features[id])
238
+ .filter(
239
+ (f): f is NonNullable<typeof f> => f !== null && f !== undefined
240
+ )
241
+ .map((f) => ({ description: f.description, id: f.id, name: f.name }));
242
+ map.set(String(vendor.id), entries);
243
+ }
244
+ return map;
245
+ });
246
+
247
+ const getVendorPurposes = function getVendorPurposes(vendorId: VendorId) {
248
+ return vendorPurposesMap.get(String(vendorId)) ?? [];
249
+ };
250
+
251
+ const getVendorSpecialPurposes = function getVendorSpecialPurposes(
252
+ vendorId: VendorId
253
+ ) {
254
+ return vendorSpecialPurposesMap.get(String(vendorId)) ?? [];
255
+ };
256
+
257
+ const getVendorSpecialFeatures = function getVendorSpecialFeatures(
258
+ vendorId: VendorId
259
+ ) {
260
+ return vendorSpecialFeaturesMap.get(String(vendorId)) ?? [];
261
+ };
262
+
263
+ const getVendorFeatures = function getVendorFeatures(vendorId: VendorId) {
264
+ return vendorFeaturesMap.get(String(vendorId)) ?? [];
265
+ };
266
+
267
+ const getMaxAgeText = function getMaxAgeText(
268
+ vendor: ProcessedVendor
269
+ ): string | null {
270
+ if (!vendor.cookieMaxAgeSeconds) {
271
+ return null;
272
+ }
273
+ let text = iabT.preferenceCenter.vendorList.maxAge.replace(
274
+ '{days}',
275
+ String(Math.floor(vendor.cookieMaxAgeSeconds / 86400))
276
+ );
277
+ if (vendor.cookieRefresh) {
278
+ text = `${text} ${iabT.preferenceCenter.vendorList.maxAgeRefreshes}`;
279
+ }
280
+ return text;
281
+ };
282
+ </script>
283
+
284
+ <div>
285
+ {#if selectedVendorId !== null}
286
+ <div class={noStyle ? '' : styles.selectedVendorBanner || ''}>
287
+ <p class={noStyle ? '' : styles.selectedVendorText || ''}>
288
+ {iabT.common.showingSelectedVendor}
289
+ </p>
290
+ <button
291
+ type="button"
292
+ onclick={onClearSelection}
293
+ class={noStyle ? '' : styles.clearSelectionButton || ''}
294
+ >
295
+ <CloseIcon
296
+ class={noStyle ? '' : styles.clearIcon || ''}
297
+ width="16"
298
+ height="16"
299
+ />
300
+ {iabT.common.clearSelection}
301
+ </button>
302
+ </div>
303
+ {:else}
304
+ <div class={noStyle ? '' : styles.vendorListHeader || ''}>
305
+ <div class={noStyle ? '' : styles.searchContainer || ''}>
306
+ <SearchIcon class={noStyle ? '' : styles.searchIcon || ''} />
307
+ <input
308
+ type="text"
309
+ placeholder={iabT.preferenceCenter.vendorList.search}
310
+ bind:value={searchTerm}
311
+ class={noStyle ? '' : styles.searchInput || ''}
312
+ />
313
+ </div>
314
+ <p class={noStyle ? '' : styles.vendorCount || ''}>
315
+ {iabT.preferenceCenter.vendorList.showingCount
316
+ .replace('{filtered}', String(filteredVendors.length))
317
+ .replace('{total}', String(vendors.length))}
318
+ </p>
319
+ </div>
320
+ {/if}
321
+
322
+ <!-- IAB Registered Vendors -->
323
+ {#if filteredIABVendors.length > 0}
324
+ <div class={noStyle ? '' : styles.vendorSection || ''}>
325
+ <div class={noStyle ? '' : styles.iabVendorSectionHeader || ''}>
326
+ <h3 class={noStyle ? '' : styles.vendorSectionHeading || ''}>
327
+ <LayersIcon class={noStyle ? '' : styles.vendorSectionIcon || ''} />
328
+ {iabT.preferenceCenter.vendorList.iabVendorsHeading}
329
+ ({filteredIABVendors.length})
330
+ </h3>
331
+ <p class={noStyle ? '' : styles.iabVendorNotice || ''}>
332
+ {iabT.preferenceCenter.vendorList.iabVendorsNotice}
333
+ </p>
334
+ </div>
335
+ <div>
336
+ {#each filteredIABVendors as vendor (vendor.id)}
337
+ {@const vendorKey = String(vendor.id)}
338
+ {@const vendorPurposes = getVendorPurposes(vendor.id)}
339
+ {@const vendorSpecialPurposes = getVendorSpecialPurposes(vendor.id)}
340
+ {@const vendorSpecialFeatures = getVendorSpecialFeatures(vendor.id)}
341
+ {@const vendorFeatures = getVendorFeatures(vendor.id)}
342
+ {@const legIntCount = vendorPurposes.filter(
343
+ (p) => p.usesLegitimateInterest
344
+ ).length}
345
+ {@const hasLegitimateInterest = vendor.legIntPurposes.length > 0}
346
+ {@const isLegitimateInterestAllowed =
347
+ vendorLegitimateInterests[vendorKey] ?? true}
348
+ {@const standardRetentionDays = vendor.dataRetention?.stdRetention}
349
+ {@const maxAgeText = getMaxAgeText(vendor)}
350
+
351
+ <PreferenceItem.Root
352
+ open={expandedVendors.has(vendor.id)}
353
+ id={`vendor-${vendorKey}`}
354
+ class={noStyle
355
+ ? ''
356
+ : `${styles.vendorListItem || ''} ${vendor.isCustom ? styles.customVendorItem || '' : ''}`}
357
+ noStyle
358
+ >
359
+ <div class={noStyle ? '' : styles.vendorListItemHeader || ''}>
360
+ <PreferenceItem.Trigger
361
+ class={noStyle ? '' : styles.vendorListTrigger || ''}
362
+ onclick={() =>
363
+ handleVendorOpenChange(
364
+ vendor.id,
365
+ !expandedVendors.has(vendor.id)
366
+ )}
367
+ >
368
+ <div class={noStyle ? '' : styles.vendorListInfo || ''}>
369
+ <h3 class={noStyle ? '' : styles.vendorListName || ''}>
370
+ {vendor.name}
371
+ </h3>
372
+ <div class={noStyle ? '' : styles.vendorListMeta || ''}>
373
+ <span
374
+ class={noStyle ? '' : styles.vendorListMetaText || ''}
375
+ >
376
+ {vendorPurposes.length} purpose{vendorPurposes.length ===
377
+ 1
378
+ ? ''
379
+ : 's'}{vendorSpecialPurposes.length > 0
380
+ ? `, ${vendorSpecialPurposes.length} special`
381
+ : ''}{vendorSpecialFeatures.length > 0
382
+ ? `, ${vendorSpecialFeatures.length} feature${vendorSpecialFeatures.length === 1 ? '' : 's'}`
383
+ : ''}
384
+ </span>
385
+ {#if legIntCount > 0}
386
+ <span
387
+ class={noStyle ? '' : styles.vendorListLiBadge || ''}
388
+ >
389
+ <LegitimateInterestIcon
390
+ style="height:0.625rem;width:0.625rem"
391
+ />
392
+ {legIntCount}
393
+ {iabT.preferenceCenter.vendorList.legitimateInterest}
394
+ </span>
395
+ {/if}
396
+ </div>
397
+ </div>
398
+ <ChevronDownIcon
399
+ class={noStyle ? '' : styles.purposeArrow || ''}
400
+ expanded={expandedVendors.has(vendor.id)}
401
+ />
402
+ </PreferenceItem.Trigger>
403
+ <div class={noStyle ? '' : styles.vendorConsentControl || ''}>
404
+ <Switch.Root
405
+ aria-label={`Consent for ${vendor.name}`}
406
+ checked={vendorConsents[vendorKey] ?? false}
407
+ onclick={() =>
408
+ onVendorToggle(
409
+ vendor.id,
410
+ !(vendorConsents[vendorKey] ?? false)
411
+ )}
412
+ class={noStyle
413
+ ? ''
414
+ : `${switchStyles.root} ${styles.vendorConsentSwitch || ''}`}
415
+ data-size="medium"
416
+ >
417
+ <Switch.Control class={noStyle ? '' : sw.track()}>
418
+ <Switch.Thumb class={noStyle ? '' : sw.thumb()} />
419
+ </Switch.Control>
420
+ </Switch.Root>
421
+ </div>
422
+ </div>
423
+
424
+ <PreferenceItem.Content
425
+ innerClassName={noStyle ? '' : styles.vendorListContent || ''}
426
+ >
427
+ <!-- Links -->
428
+ <div class={noStyle ? '' : styles.vendorLinks || ''}>
429
+ <a
430
+ href={vendor.policyUrl}
431
+ target="_blank"
432
+ rel="noopener noreferrer"
433
+ class={noStyle ? '' : styles.vendorLink || ''}
434
+ >
435
+ <ExternalLinkIcon
436
+ class={noStyle ? '' : styles.vendorLinkIcon || ''}
437
+ />
438
+ {iabT.preferenceCenter.vendorList.privacyPolicy}
439
+ </a>
440
+ {#if vendor.legitimateInterestUrl}
441
+ <a
442
+ href={vendor.legitimateInterestUrl}
443
+ target="_blank"
444
+ rel="noopener noreferrer"
445
+ class={noStyle ? '' : styles.vendorLink || ''}
446
+ >
447
+ <ExternalLinkIcon
448
+ class={noStyle ? '' : styles.vendorLinkIcon || ''}
449
+ />
450
+ {iabT.preferenceCenter.purposeItem.legitimateInterest}
451
+ </a>
452
+ {/if}
453
+ {#if vendor.deviceStorageDisclosureUrl}
454
+ <a
455
+ href={vendor.deviceStorageDisclosureUrl}
456
+ target="_blank"
457
+ rel="noopener noreferrer"
458
+ class={noStyle ? '' : styles.vendorLink || ''}
459
+ >
460
+ <ExternalLinkIcon
461
+ class={noStyle ? '' : styles.vendorLinkIcon || ''}
462
+ />
463
+ {iabT.preferenceCenter.vendorList.storageDisclosure}
464
+ </a>
465
+ {/if}
466
+ </div>
467
+
468
+ <!-- Badges -->
469
+ <div class={noStyle ? '' : styles.vendorBadges || ''}>
470
+ {#if vendor.usesCookies}
471
+ <span class={noStyle ? '' : styles.vendorBadge || ''}>
472
+ {iabT.preferenceCenter.vendorList.usesCookies}
473
+ </span>
474
+ {/if}
475
+ {#if vendor.usesNonCookieAccess}
476
+ <span class={noStyle ? '' : styles.vendorBadge || ''}>
477
+ {iabT.preferenceCenter.vendorList.nonCookieAccess}
478
+ </span>
479
+ {/if}
480
+ {#if maxAgeText}
481
+ <span class={noStyle ? '' : styles.vendorBadge || ''}>
482
+ {maxAgeText}
483
+ </span>
484
+ {/if}
485
+ {#if standardRetentionDays}
486
+ <span class={noStyle ? '' : styles.vendorBadge || ''}>
487
+ {iabT.preferenceCenter.vendorList.retention.replace(
488
+ '{days}',
489
+ String(standardRetentionDays)
490
+ )}
491
+ </span>
492
+ {/if}
493
+ </div>
494
+
495
+ <!-- Purposes -->
496
+ {#if vendorPurposes.length > 0}
497
+ <div class={noStyle ? '' : styles.vendorPurposesList || ''}>
498
+ <h4 class={noStyle ? '' : styles.vendorPurposesTitle || ''}>
499
+ {iabT.preferenceCenter.vendorList.purposes}
500
+ ({vendorPurposes.length})
501
+ </h4>
502
+ <ul class={noStyle ? '' : styles.vendorPurposesItems || ''}>
503
+ {#each vendorPurposes as purpose (purpose.id)}
504
+ {@const retentionDays =
505
+ vendor.dataRetention?.purposes?.[purpose.id] ??
506
+ vendor.dataRetention?.stdRetention}
507
+ <li
508
+ class={noStyle
509
+ ? ''
510
+ : `${styles.vendorPurposeItem || ''} ${purpose.usesLegitimateInterest ? styles.vendorPurposeItemLi || '' : ''}`}
511
+ >
512
+ <span>
513
+ {purpose.name}
514
+ {#if retentionDays}
515
+ <span
516
+ class={noStyle
517
+ ? ''
518
+ : styles.vendorRetention || ''}
519
+ >
520
+ ({iabT.preferenceCenter.vendorList.retainedDays.replace(
521
+ '{days}',
522
+ String(retentionDays)
523
+ )})
524
+ </span>
525
+ {/if}
526
+ </span>
527
+ {#if purpose.usesLegitimateInterest}
528
+ <span
529
+ class={noStyle
530
+ ? ''
531
+ : styles.vendorListLiBadge || ''}
532
+ >
533
+ <LegitimateInterestIcon
534
+ width="10"
535
+ height="10"
536
+ />
537
+ {iabT.preferenceCenter.vendorList
538
+ .legitimateInterest}
539
+ </span>
540
+ {/if}
541
+ </li>
542
+ {/each}
543
+ </ul>
544
+ </div>
545
+ {/if}
546
+
547
+ <!-- Legitimate Interest Objection -->
548
+ {#if hasLegitimateInterest && onVendorLegitimateInterestToggle}
549
+ <div class={noStyle ? '' : styles.vendorLiSection || ''}>
550
+ <div
551
+ class={noStyle ? '' : styles.vendorLiSectionHeader || ''}
552
+ >
553
+ <h4 class={noStyle ? '' : styles.vendorPurposesTitle || ''}>
554
+ <LegitimateInterestIcon
555
+ class={noStyle
556
+ ? ''
557
+ : styles.legitimateInterestIcon || ''}
558
+ />
559
+ {iabT.preferenceCenter.purposeItem.legitimateInterest}
560
+ </h4>
561
+ <button
562
+ type="button"
563
+ onclick={() =>
564
+ onVendorLegitimateInterestToggle?.(
565
+ vendor.id,
566
+ !isLegitimateInterestAllowed
567
+ )}
568
+ class={noStyle
569
+ ? ''
570
+ : `${styles.objectButton || ''} ${!isLegitimateInterestAllowed ? styles.objectButtonActive || '' : ''}`}
571
+ aria-pressed={!isLegitimateInterestAllowed}
572
+ >
573
+ {isLegitimateInterestAllowed
574
+ ? iabT.preferenceCenter.purposeItem.objectButton
575
+ : iabT.preferenceCenter.purposeItem.objected}
576
+ </button>
577
+ </div>
578
+ <p class={noStyle ? '' : styles.liExplanation || ''}>
579
+ {iabT.preferenceCenter.purposeItem.rightToObject}
580
+ </p>
581
+ </div>
582
+ {/if}
583
+
584
+ <!-- Data Categories -->
585
+ {#if vendor.dataDeclaration && vendor.dataDeclaration.length > 0}
586
+ <div class={noStyle ? '' : styles.vendorPurposesList || ''}>
587
+ <h4 class={noStyle ? '' : styles.vendorPurposesTitle || ''}>
588
+ {iabT.preferenceCenter.vendorList.dataCategories}
589
+ ({vendor.dataDeclaration.length})
590
+ </h4>
591
+ <ul class={noStyle ? '' : styles.vendorPurposesItems || ''}>
592
+ {#each vendor.dataDeclaration as categoryId (categoryId)}
593
+ {@const category =
594
+ vendorData?.dataCategories?.[categoryId]}
595
+ <li
596
+ class={noStyle ? '' : styles.vendorPurposeItem || ''}
597
+ title={category?.description}
598
+ >
599
+ {category?.name || `Data Category ${categoryId}`}
600
+ </li>
601
+ {/each}
602
+ </ul>
603
+ </div>
604
+ {/if}
605
+
606
+ <!-- Special Purposes -->
607
+ {#if vendorSpecialPurposes.length > 0}
608
+ <div class={noStyle ? '' : styles.vendorPurposesList || ''}>
609
+ <h4 class={noStyle ? '' : styles.vendorPurposesTitle || ''}>
610
+ <LockIcon
611
+ aria-label={iabT.preferenceCenter.vendorList
612
+ .specialPurposes}
613
+ role="img"
614
+ class={noStyle ? '' : styles.legitimateInterestIcon || ''}
615
+ />
616
+ {iabT.preferenceCenter.vendorList.specialPurposes}
617
+ ({vendorSpecialPurposes.length})
618
+ </h4>
619
+ <ul class={noStyle ? '' : styles.vendorPurposesItems || ''}>
620
+ {#each vendorSpecialPurposes as sp (sp.id)}
621
+ {@const retentionDays =
622
+ vendor.dataRetention?.specialPurposes?.[sp.id] ??
623
+ vendor.dataRetention?.stdRetention}
624
+ <li class={noStyle ? '' : styles.vendorPurposeItem || ''}>
625
+ <span>
626
+ {sp.name}
627
+ {#if retentionDays}
628
+ <span
629
+ class={noStyle
630
+ ? ''
631
+ : styles.vendorRetention || ''}
632
+ >
633
+ ({iabT.preferenceCenter.vendorList.retainedDays.replace(
634
+ '{days}',
635
+ String(retentionDays)
636
+ )})
637
+ </span>
638
+ {/if}
639
+ </span>
640
+ </li>
641
+ {/each}
642
+ </ul>
643
+ <p class={noStyle ? '' : styles.liExplanation || ''}>
644
+ {iabT.preferenceCenter.vendorList.requiredNotice}
645
+ </p>
646
+ </div>
647
+ {/if}
648
+
649
+ <!-- Special Features -->
650
+ {#if vendorSpecialFeatures.length > 0}
651
+ <div class={noStyle ? '' : styles.vendorPurposesList || ''}>
652
+ <h4 class={noStyle ? '' : styles.vendorPurposesTitle || ''}>
653
+ {iabT.preferenceCenter.vendorList.specialFeatures}
654
+ ({vendorSpecialFeatures.length})
655
+ </h4>
656
+ <ul class={noStyle ? '' : styles.vendorPurposesItems || ''}>
657
+ {#each vendorSpecialFeatures as sf (sf.id)}
658
+ <li class={noStyle ? '' : styles.vendorPurposeItem || ''}>
659
+ {sf.name}
660
+ </li>
661
+ {/each}
662
+ </ul>
663
+ </div>
664
+ {/if}
665
+
666
+ <!-- Features -->
667
+ {#if vendorFeatures.length > 0}
668
+ <div class={noStyle ? '' : styles.vendorPurposesList || ''}>
669
+ <h4 class={noStyle ? '' : styles.vendorPurposesTitle || ''}>
670
+ {iabT.preferenceCenter.vendorList.features}
671
+ ({vendorFeatures.length})
672
+ </h4>
673
+ <ul class={noStyle ? '' : styles.vendorPurposesItems || ''}>
674
+ {#each vendorFeatures as f (f.id)}
675
+ <li class={noStyle ? '' : styles.vendorPurposeItem || ''}>
676
+ {f.name}
677
+ </li>
678
+ {/each}
679
+ </ul>
680
+ </div>
681
+ {/if}
682
+ </PreferenceItem.Content>
683
+ </PreferenceItem.Root>
684
+ {/each}
685
+ </div>
686
+ </div>
687
+ {/if}
688
+
689
+ <!-- Custom/Non-IAB Vendors -->
690
+ {#if filteredCustomVendors.length > 0}
691
+ <div class={noStyle ? '' : styles.vendorSection || ''}>
692
+ <div class={noStyle ? '' : styles.customVendorSectionHeader || ''}>
693
+ <h3 class={noStyle ? '' : styles.vendorSectionHeading || ''}>
694
+ <GlobeIcon class={noStyle ? '' : styles.vendorSectionIcon || ''} />
695
+ {iabT.preferenceCenter.vendorList.customVendorsHeading}
696
+ ({filteredCustomVendors.length})
697
+ </h3>
698
+ <p class={noStyle ? '' : styles.customVendorNotice || ''}>
699
+ {iabT.preferenceCenter.vendorList.customVendorsNotice}
700
+ </p>
701
+ </div>
702
+ <div>
703
+ {#each filteredCustomVendors as vendor (vendor.id)}
704
+ {@const vendorKey = String(vendor.id)}
705
+ {@const vendorPurposes = getVendorPurposes(vendor.id)}
706
+ {@const vendorSpecialFeatures = getVendorSpecialFeatures(vendor.id)}
707
+ {@const vendorFeatures = getVendorFeatures(vendor.id)}
708
+ {@const hasLegitimateInterest = vendor.legIntPurposes.length > 0}
709
+ {@const isLegitimateInterestAllowed =
710
+ vendorLegitimateInterests[vendorKey] ?? true}
711
+ {@const maxAgeText = getMaxAgeText(vendor)}
712
+
713
+ <PreferenceItem.Root
714
+ open={expandedVendors.has(vendor.id)}
715
+ id={`vendor-${vendorKey}`}
716
+ class={noStyle
717
+ ? ''
718
+ : `${styles.vendorListItem || ''} ${styles.customVendorItem || ''}`}
719
+ noStyle
720
+ >
721
+ <div class={noStyle ? '' : styles.vendorListItemHeader || ''}>
722
+ <PreferenceItem.Trigger
723
+ class={noStyle ? '' : styles.vendorListTrigger || ''}
724
+ onclick={() =>
725
+ handleVendorOpenChange(
726
+ vendor.id,
727
+ !expandedVendors.has(vendor.id)
728
+ )}
729
+ >
730
+ <div class={noStyle ? '' : styles.vendorListInfo || ''}>
731
+ <h3 class={noStyle ? '' : styles.vendorListName || ''}>
732
+ {vendor.name}
733
+ <GlobeIcon
734
+ class={noStyle ? '' : styles.customVendorIcon || ''}
735
+ aria-label={iabT.common.customPartner}
736
+ />
737
+ </h3>
738
+ </div>
739
+ <ChevronDownIcon
740
+ class={noStyle ? '' : styles.purposeArrow || ''}
741
+ expanded={expandedVendors.has(vendor.id)}
742
+ />
743
+ </PreferenceItem.Trigger>
744
+ <div class={noStyle ? '' : styles.vendorConsentControl || ''}>
745
+ <Switch.Root
746
+ aria-label={`Consent for ${vendor.name}`}
747
+ checked={vendorConsents[vendorKey] ?? false}
748
+ onclick={() =>
749
+ onVendorToggle(
750
+ vendor.id,
751
+ !(vendorConsents[vendorKey] ?? false)
752
+ )}
753
+ class={noStyle
754
+ ? ''
755
+ : `${switchStyles.root} ${styles.vendorConsentSwitch || ''}`}
756
+ data-size="medium"
757
+ >
758
+ <Switch.Control class={noStyle ? '' : sw.track()}>
759
+ <Switch.Thumb class={noStyle ? '' : sw.thumb()} />
760
+ </Switch.Control>
761
+ </Switch.Root>
762
+ </div>
763
+ </div>
764
+
765
+ <PreferenceItem.Content
766
+ innerClassName={noStyle ? '' : styles.vendorListContent || ''}
767
+ >
768
+ <div class={noStyle ? '' : styles.vendorLinks || ''}>
769
+ <a
770
+ href={vendor.policyUrl}
771
+ target="_blank"
772
+ rel="noopener noreferrer"
773
+ class={noStyle ? '' : styles.vendorLink || ''}
774
+ >
775
+ <ExternalLinkIcon
776
+ class={noStyle ? '' : styles.vendorLinkIcon || ''}
777
+ />
778
+ {iabT.preferenceCenter.vendorList.privacyPolicy}
779
+ </a>
780
+ </div>
781
+
782
+ <div class={noStyle ? '' : styles.vendorBadges || ''}>
783
+ {#if vendor.usesCookies}
784
+ <span class={noStyle ? '' : styles.vendorBadge || ''}
785
+ >{iabT.preferenceCenter.vendorList.usesCookies}</span
786
+ >
787
+ {/if}
788
+ {#if vendor.usesNonCookieAccess}
789
+ <span class={noStyle ? '' : styles.vendorBadge || ''}
790
+ >{iabT.preferenceCenter.vendorList.nonCookieAccess}</span
791
+ >
792
+ {/if}
793
+ {#if maxAgeText}
794
+ <span class={noStyle ? '' : styles.vendorBadge || ''}
795
+ >{maxAgeText}</span
796
+ >
797
+ {/if}
798
+ </div>
799
+
800
+ {#if vendorPurposes.length > 0}
801
+ <div class={noStyle ? '' : styles.vendorPurposesList || ''}>
802
+ <h4 class={noStyle ? '' : styles.vendorPurposesTitle || ''}>
803
+ {iabT.preferenceCenter.vendorList.purposes}
804
+ ({vendorPurposes.length})
805
+ </h4>
806
+ <ul class={noStyle ? '' : styles.vendorPurposesItems || ''}>
807
+ {#each vendorPurposes as purpose (purpose.id)}
808
+ <li class={noStyle ? '' : styles.vendorPurposeItem || ''}>
809
+ {purpose.name}
810
+ </li>
811
+ {/each}
812
+ </ul>
813
+ </div>
814
+ {/if}
815
+
816
+ {#if hasLegitimateInterest && onVendorLegitimateInterestToggle}
817
+ <div class={noStyle ? '' : styles.vendorLiSection || ''}>
818
+ <div
819
+ class={noStyle ? '' : styles.vendorLiSectionHeader || ''}
820
+ >
821
+ <h4 class={noStyle ? '' : styles.vendorPurposesTitle || ''}>
822
+ <LegitimateInterestIcon
823
+ class={noStyle
824
+ ? ''
825
+ : styles.legitimateInterestIcon || ''}
826
+ />
827
+ {iabT.preferenceCenter.purposeItem.legitimateInterest}
828
+ </h4>
829
+ <button
830
+ type="button"
831
+ onclick={() =>
832
+ onVendorLegitimateInterestToggle?.(
833
+ vendor.id,
834
+ !isLegitimateInterestAllowed
835
+ )}
836
+ class={noStyle
837
+ ? ''
838
+ : `${styles.objectButton || ''} ${!isLegitimateInterestAllowed ? styles.objectButtonActive || '' : ''}`}
839
+ aria-pressed={!isLegitimateInterestAllowed}
840
+ >
841
+ {isLegitimateInterestAllowed
842
+ ? iabT.preferenceCenter.purposeItem.objectButton
843
+ : iabT.preferenceCenter.purposeItem.objected}
844
+ </button>
845
+ </div>
846
+ <p class={noStyle ? '' : styles.liExplanation || ''}>
847
+ {iabT.preferenceCenter.purposeItem.rightToObject}
848
+ </p>
849
+ </div>
850
+ {/if}
851
+
852
+ {#if vendorSpecialFeatures.length > 0}
853
+ <div class={noStyle ? '' : styles.vendorPurposesList || ''}>
854
+ <h4 class={noStyle ? '' : styles.vendorPurposesTitle || ''}>
855
+ {iabT.preferenceCenter.vendorList.specialFeatures}
856
+ ({vendorSpecialFeatures.length})
857
+ </h4>
858
+ <ul class={noStyle ? '' : styles.vendorPurposesItems || ''}>
859
+ {#each vendorSpecialFeatures as sf (sf.id)}
860
+ <li class={noStyle ? '' : styles.vendorPurposeItem || ''}>
861
+ {sf.name}
862
+ </li>
863
+ {/each}
864
+ </ul>
865
+ </div>
866
+ {/if}
867
+
868
+ {#if vendorFeatures.length > 0}
869
+ <div class={noStyle ? '' : styles.vendorPurposesList || ''}>
870
+ <h4 class={noStyle ? '' : styles.vendorPurposesTitle || ''}>
871
+ {iabT.preferenceCenter.vendorList.features}
872
+ ({vendorFeatures.length})
873
+ </h4>
874
+ <ul class={noStyle ? '' : styles.vendorPurposesItems || ''}>
875
+ {#each vendorFeatures as f (f.id)}
876
+ <li class={noStyle ? '' : styles.vendorPurposeItem || ''}>
877
+ {f.name}
878
+ </li>
879
+ {/each}
880
+ </ul>
881
+ </div>
882
+ {/if}
883
+ </PreferenceItem.Content>
884
+ </PreferenceItem.Root>
885
+ {/each}
886
+ </div>
887
+ </div>
888
+ {/if}
889
+
890
+ {#if filteredVendors.length === 0}
891
+ <div class={noStyle ? '' : styles.emptyState || ''}>
892
+ <p class={noStyle ? '' : styles.emptyStateText || ''}>
893
+ {iabT.preferenceCenter.vendorList.noVendorsFound.replace(
894
+ '{searchTerm}',
895
+ searchTerm
896
+ )}
897
+ </p>
898
+ </div>
899
+ {/if}
900
+ </div>