@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,95 @@
1
+ <script lang="ts">
2
+ import type { LegalLinks as LegalLinksType } from '@c15t/core';
3
+ import { defaultTranslationConfig } from '@c15t/core';
4
+ import legalLinkStyles from '@c15t/ui/styles/components/legal-links';
5
+ import type { AllThemeKeys } from '@c15t/ui/theme';
6
+ import { resolveTranslations } from '@c15t/ui/utils';
7
+
8
+ import { getConsentContext, getThemeContext } from '../context.svelte';
9
+ import { resolveComponentStyles } from '../utils';
10
+
11
+ let {
12
+ links,
13
+ themeKey = 'consentBannerDescription' as AllThemeKeys,
14
+ testIdPrefix,
15
+ }: {
16
+ links?: (keyof LegalLinksType)[] | null;
17
+ themeKey?: AllThemeKeys;
18
+ testIdPrefix?: string;
19
+ } = $props();
20
+
21
+ const consent = getConsentContext();
22
+ const theme = getThemeContext();
23
+
24
+ const noStyle = $derived(theme.noStyle ?? false);
25
+
26
+ const translations = $derived(
27
+ resolveTranslations(
28
+ consent.state.translationConfig,
29
+ defaultTranslationConfig
30
+ )
31
+ );
32
+
33
+ const filteredLinks = $derived.by(() => {
34
+ if (links === undefined || links === null) {
35
+ return null;
36
+ }
37
+ const allLinks = consent.state.legalLinks;
38
+ if (!allLinks) {
39
+ return null;
40
+ }
41
+ const entries = Object.entries(allLinks).filter(([key]) =>
42
+ links.includes(key as keyof LegalLinksType)
43
+ );
44
+ return entries.length > 0
45
+ ? (Object.fromEntries(entries) as LegalLinksType)
46
+ : null;
47
+ });
48
+
49
+ const linkStyle = $derived(
50
+ resolveComponentStyles(
51
+ themeKey,
52
+ theme.theme,
53
+ { baseClassName: legalLinkStyles.legalLink },
54
+ noStyle
55
+ )
56
+ );
57
+
58
+ const linkEntries = $derived(
59
+ filteredLinks
60
+ ? (Object.entries(filteredLinks) as [
61
+ keyof LegalLinksType,
62
+ LegalLinksType[keyof LegalLinksType],
63
+ ][])
64
+ : []
65
+ );
66
+ </script>
67
+
68
+ {#if linkEntries.length > 0}
69
+ <span>
70
+ &nbsp;
71
+ {#each linkEntries as [type, link], index (type)}
72
+ {#if link}
73
+ <span>
74
+ <a
75
+ href={link.href}
76
+ target={link.target || '_blank'}
77
+ rel={link.rel ||
78
+ (link.target === '_blank' ? 'noopener noreferrer' : undefined)}
79
+ class={linkStyle.className || ''}
80
+ data-testid={testIdPrefix ? `${testIdPrefix}-${type}` : undefined}
81
+ >
82
+ {link.label ??
83
+ (translations.legalLinks as Record<string, string>)?.[
84
+ type as string
85
+ ] ??
86
+ type}{index < linkEntries.length - 1 ? ',' : ''}
87
+ </a>
88
+ {#if index < linkEntries.length - 1}
89
+ &nbsp;
90
+ {/if}
91
+ </span>
92
+ {/if}
93
+ {/each}
94
+ </span>
95
+ {/if}
@@ -0,0 +1,10 @@
1
+ import type { LegalLinks as LegalLinksType } from '@c15t/core';
2
+ import type { AllThemeKeys } from '@c15t/ui/theme';
3
+ type $$ComponentProps = {
4
+ links?: (keyof LegalLinksType)[] | null;
5
+ themeKey?: AllThemeKeys;
6
+ testIdPrefix?: string;
7
+ };
8
+ declare const InlineLegalLinks: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type InlineLegalLinks = ReturnType<typeof InlineLegalLinks>;
10
+ export default InlineLegalLinks;
@@ -0,0 +1,459 @@
1
+ <script lang="ts">
2
+ import type {
3
+ AllConsentNames,
4
+ ConsentSnapshot,
5
+ ConsentState,
6
+ KernelOverrides,
7
+ KernelUser,
8
+ } from '@c15t/core';
9
+ import {
10
+ createConsentRuntime,
11
+ normalizeKernelUser,
12
+ } from '@c15t/core/runtime';
13
+ import type { ConsentRuntime } from '@c15t/core/runtime';
14
+ import type { IABHandle } from '@c15t/iab';
15
+ import { generateThemeCSS } from '@c15t/ui/theme';
16
+ import { setupColorScheme } from '@c15t/ui/utils';
17
+ import type { Snippet } from 'svelte';
18
+ import { onDestroy, onMount, untrack } from 'svelte';
19
+
20
+ import { setConsentContext, setThemeContext } from '../context.svelte';
21
+ import type { ConsentDraftState, SvelteIABState } from '../context.svelte';
22
+ import { isIABConfigured, lazyCreateIAB } from '../iab-loader';
23
+ import type { ConsentManagerOptions } from '../types';
24
+
25
+ type ProviderOptionsInput = Omit<ConsentManagerOptions, 'mode'> & {
26
+ mode?: ConsentManagerOptions['mode'];
27
+ };
28
+
29
+ interface ProviderRuntimeProps {
30
+ children?: Snippet;
31
+ /**
32
+ * An externally owned runtime to render instead of creating one.
33
+ *
34
+ * A SvelteKit root layout or an Astro page can create a single
35
+ * runtime with `createConsentRuntime()` and share it across
36
+ * component trees that cannot see each other's context. The
37
+ * provider neither starts nor disposes a runtime it did not
38
+ * create — the owner does both.
39
+ */
40
+ runtime?: ConsentRuntime;
41
+ }
42
+
43
+ type ConsentManagerProviderProps =
44
+ | (ConsentManagerOptions &
45
+ ProviderRuntimeProps & {
46
+ options?: ProviderOptionsInput;
47
+ })
48
+ | (ProviderOptionsInput &
49
+ ProviderRuntimeProps & {
50
+ options: ConsentManagerOptions;
51
+ });
52
+
53
+ let props: ConsentManagerProviderProps = $props();
54
+
55
+ const mergeDefinedOptions = function mergeDefinedOptions(
56
+ base: ProviderOptionsInput,
57
+ overrides: ProviderOptionsInput
58
+ ): ProviderOptionsInput {
59
+ const merged = { ...base };
60
+ for (const [key, value] of Object.entries(overrides) as [
61
+ keyof ConsentManagerOptions,
62
+ ConsentManagerOptions[keyof ConsentManagerOptions],
63
+ ][]) {
64
+ if (value !== undefined) {
65
+ merged[key] = value as never;
66
+ }
67
+ }
68
+ return merged;
69
+ };
70
+
71
+ const resolveProviderOptions = function resolveProviderOptions({
72
+ children: _children,
73
+ runtime: _runtime,
74
+ options: nestedOptions = {},
75
+ ...topLevelOptions
76
+ }: ConsentManagerProviderProps): ProviderOptionsInput {
77
+ return mergeDefinedOptions(nestedOptions, topLevelOptions);
78
+ };
79
+
80
+ const children = $derived(props.children);
81
+ const options = $derived(resolveProviderOptions(props));
82
+
83
+ // The runtime owns the kernel and every side-effecting module. When one
84
+ // is handed in, its owner is also responsible for `start()`/`dispose()`.
85
+ const externalRuntime = untrack(() => props.runtime);
86
+ const ownsRuntime = externalRuntime === undefined;
87
+ const runtime: ConsentRuntime =
88
+ externalRuntime ??
89
+ untrack(() =>
90
+ createConsentRuntime({
91
+ ...options,
92
+ // Only an app that configured IAB reaches for `@c15t/iab`, and
93
+ // even then the module arrives through a dynamic import.
94
+ createIAB: isIABConfigured(options.iab) ? lazyCreateIAB : undefined,
95
+ mode: options.mode as ConsentManagerOptions['mode'],
96
+ pkg: '@c15t/svelte',
97
+ })
98
+ );
99
+ const { kernel } = runtime;
100
+
101
+ let snapshot = $state<ConsentSnapshot>(kernel.getSnapshot());
102
+ let draftFingerprint = $state<string | null>(null);
103
+ let draftRevision = 0;
104
+ let draftSaveSequence = 0;
105
+ let draftValues = $state<Partial<ConsentState>>({});
106
+ let iabHandle = $state<IABHandle | null>(
107
+ untrack(() => runtime.iab as IABHandle | null)
108
+ );
109
+ let iabTab = $state<'purposes' | 'vendors'>('purposes');
110
+ let configuredCategories = $state<AllConsentNames[]>(
111
+ untrack(() => options.consentCategories ?? runtime.consentCategories)
112
+ );
113
+
114
+ const draft: ConsentDraftState = {
115
+ get isStale() {
116
+ return (
117
+ draftFingerprint !== null &&
118
+ draftFingerprint !== snapshot.evaluationPolicy.choice.fingerprint
119
+ );
120
+ },
121
+ reset() {
122
+ draftRevision += 1;
123
+ draftValues = {};
124
+ draftFingerprint = null;
125
+ },
126
+ async save(categories) {
127
+ const revision = draftRevision;
128
+ draftSaveSequence += 1;
129
+ const sequence = draftSaveSequence;
130
+ const current = kernel.getSnapshot();
131
+ if (
132
+ draftFingerprint !== null &&
133
+ draftFingerprint !== current.evaluationPolicy.choice.fingerprint
134
+ ) {
135
+ throw new Error(
136
+ 'The policy changed. Review your preferences before saving.'
137
+ );
138
+ }
139
+ const { values } = draft;
140
+ const result = await kernel.commands.save(
141
+ Object.fromEntries(
142
+ current.policyRule.scope
143
+ .filter(
144
+ (name) =>
145
+ configuredCategories.length === 0 ||
146
+ configuredCategories.includes(name)
147
+ )
148
+ .filter(
149
+ (name) => categories === undefined || categories.includes(name)
150
+ )
151
+ .map((name) => [name, values[name]])
152
+ )
153
+ );
154
+ if (!result.ok) {
155
+ throw new Error('Unable to save preferences.');
156
+ }
157
+ if (revision === draftRevision && sequence === draftSaveSequence) {
158
+ draft.reset();
159
+ }
160
+ },
161
+ set(name, value) {
162
+ if (name === 'necessary') {
163
+ return;
164
+ }
165
+ draftRevision += 1;
166
+ draftFingerprint ??=
167
+ kernel.getSnapshot().evaluationPolicy.choice.fingerprint;
168
+ draftValues = { ...draftValues, [name]: value };
169
+ },
170
+ get values() {
171
+ return {
172
+ necessary: true,
173
+ ...Object.fromEntries(
174
+ snapshot.policyRule.scope.map((name) => [
175
+ name,
176
+ draftValues[name] ??
177
+ snapshot.explicitChoice?.categories[name]?.value ??
178
+ options.presentation?.preferences?.defaults?.[name] ??
179
+ (snapshot.policyRule.model === 'opt-out' ||
180
+ snapshot.policyRule.preselectedCategories.includes(name)),
181
+ ])
182
+ ),
183
+ };
184
+ },
185
+ };
186
+
187
+ const getIABState = function getIABState(): SvelteIABState | null {
188
+ const { iab } = snapshot;
189
+ if (!iab) {
190
+ return null;
191
+ }
192
+ // Rendering keys on the kernel state so a server-resolved GVL puts the
193
+ // IAB surfaces in the first HTML. Until `@c15t/iab` lands the lazy
194
+ // handle queues calls and replays them, so an early Accept still
195
+ // records consent; without any handle the actions are no-ops.
196
+ const readyHandle = iabHandle;
197
+ const noop = () => {
198
+ /* empty */
199
+ };
200
+ const noopAsync = async () => {
201
+ /* empty */
202
+ };
203
+ return {
204
+ ...iab,
205
+ acceptAll: readyHandle?.acceptAll ?? noop,
206
+ config: {
207
+ cmpId: iab.cmpId,
208
+ enabled: iab.enabled,
209
+ },
210
+ isLoadingGVL: iab.enabled && !iab.gvl,
211
+ nonIABVendors: iab.customVendors,
212
+ preferenceCenterTab: iabTab,
213
+ rejectAll: readyHandle?.rejectAll ?? noop,
214
+ save: readyHandle?.save ?? noopAsync,
215
+ setPreferenceCenterTab(tab) {
216
+ iabTab = tab;
217
+ },
218
+ setPurposeConsent: readyHandle?.setPurposeConsent ?? noop,
219
+ setPurposeLegitimateInterest:
220
+ readyHandle?.setPurposeLegitimateInterest ?? noop,
221
+ setSpecialFeatureOptIn: readyHandle?.setSpecialFeatureOptIn ?? noop,
222
+ setVendorConsent: readyHandle?.setVendorConsent ?? noop,
223
+ setVendorLegitimateInterest:
224
+ readyHandle?.setVendorLegitimateInterest ?? noop,
225
+ };
226
+ };
227
+
228
+ setConsentContext(kernel, {
229
+ clearRecords: () => runtime.clearRecords(),
230
+ getConsentCategories: () => [
231
+ 'necessary',
232
+ ...snapshot.policyRule.scope.filter(
233
+ (name) =>
234
+ !configuredCategories.length || configuredCategories.includes(name)
235
+ ),
236
+ ],
237
+ getDraft: () => draft,
238
+ getIAB: getIABState,
239
+ getLegalLinks: () => options.legalLinks,
240
+ getPresentation: () => options.presentation,
241
+ getSnapshot: () => snapshot,
242
+ });
243
+
244
+ const unsubscribe = kernel.subscribe((next) => {
245
+ snapshot = next;
246
+ });
247
+
248
+ // The lazy handle queues calls until `@c15t/iab` lands and replays them,
249
+ // so the surfaces render against it as soon as it exists.
250
+ const unsubscribeIAB = runtime.onIABChange((next) => {
251
+ iabHandle = next as IABHandle | null;
252
+ });
253
+
254
+ onMount(() => {
255
+ if (!ownsRuntime) {
256
+ return;
257
+ }
258
+ runtime.start();
259
+ snapshot = kernel.getSnapshot();
260
+ return () => {
261
+ // Drop the IAB listener first: disposing the runtime emits a
262
+ // final `null` and this component is already tearing down.
263
+ unsubscribeIAB();
264
+ runtime.dispose();
265
+ };
266
+ });
267
+
268
+ // Each of the effects below reads one narrow value rather than the whole
269
+ // derived `options` object. Reading `options` would tie them to every
270
+ // prop — a new inline `options={{ theme }}` would re-run `identify()`
271
+ // and fire a second `init()` on a theme change.
272
+ const userOption = $derived(options.user);
273
+ const overridesOption = $derived(options.overrides);
274
+ const consentCategoriesOption = $derived(options.consentCategories);
275
+ const enabledOption = $derived(options.enabled ?? true);
276
+
277
+ // Every field `identify()` sends, in a fixed order. Keying on a subset
278
+ // would swallow an update: same `externalId`, new `properties`, no call.
279
+ const userKey = function userKey(
280
+ user: KernelUser | undefined
281
+ ): string | null {
282
+ if (!user) {
283
+ return null;
284
+ }
285
+ return JSON.stringify([
286
+ user.externalId,
287
+ user.externalIdType,
288
+ user.identityProvider,
289
+ user.properties,
290
+ ]);
291
+ };
292
+
293
+ // What the runtime carried before this provider pushed anything, so
294
+ // removing the prop restores that rather than leaving the last pushed
295
+ // list in place. Only restored if this provider did the pushing: a
296
+ // borrowed runtime's categories belong to whoever owns it.
297
+ const initialCategories = untrack(() => runtime.consentCategories);
298
+ let pushedCategories = false;
299
+
300
+ $effect(() => {
301
+ configuredCategories = consentCategoriesOption ?? initialCategories;
302
+ if (consentCategoriesOption) {
303
+ runtime.setConsentCategories(consentCategoriesOption);
304
+ pushedCategories = true;
305
+ return;
306
+ }
307
+ if (pushedCategories) {
308
+ runtime.setConsentCategories(initialCategories);
309
+ pushedCategories = false;
310
+ }
311
+ });
312
+
313
+ let lastIdentifiedKey: string | null = null;
314
+
315
+ $effect(() => {
316
+ if (!ownsRuntime) {
317
+ return;
318
+ }
319
+ const nextUser = normalizeKernelUser(userOption);
320
+ const key = userKey(nextUser);
321
+ if (key === null || key === lastIdentifiedKey) {
322
+ return;
323
+ }
324
+ lastIdentifiedKey = key;
325
+ void runtime.identify(nextUser);
326
+ });
327
+
328
+ let lastOverridesKey: string | null = null;
329
+ let hasSkippedInitialOverridesInit = false;
330
+
331
+ $effect(() => {
332
+ if (!ownsRuntime) {
333
+ return;
334
+ }
335
+ const overrides: KernelOverrides = overridesOption ?? {};
336
+ const key = JSON.stringify(
337
+ Object.entries(overrides).sort(([left], [right]) =>
338
+ left.localeCompare(right)
339
+ )
340
+ );
341
+ if (key === lastOverridesKey) {
342
+ return;
343
+ }
344
+ lastOverridesKey = key;
345
+ if (!hasSkippedInitialOverridesInit) {
346
+ hasSkippedInitialOverridesInit = true;
347
+ return;
348
+ }
349
+ runtime.setOverrides(overrides);
350
+ if (enabledOption) {
351
+ void runtime.reinit();
352
+ }
353
+ });
354
+
355
+ $effect(() => {
356
+ if (!ownsRuntime || enabledOption) {
357
+ return;
358
+ }
359
+ kernel.set.activeUI('none');
360
+ });
361
+
362
+ let prefersReducedMotion = $state(false);
363
+
364
+ onMount(() => {
365
+ if (typeof window === 'undefined' || !window.matchMedia) {
366
+ return;
367
+ }
368
+ const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
369
+ prefersReducedMotion = mediaQuery.matches;
370
+
371
+ const handler = (event: MediaQueryListEvent) => {
372
+ prefersReducedMotion = event.matches;
373
+ };
374
+ mediaQuery.addEventListener('change', handler);
375
+ return () => mediaQuery.removeEventListener('change', handler);
376
+ });
377
+
378
+ const userTheme = $derived(options.theme);
379
+
380
+ setThemeContext({
381
+ get colorScheme() {
382
+ return options.colorScheme;
383
+ },
384
+ get disableAnimation() {
385
+ return options.disableAnimation ?? prefersReducedMotion;
386
+ },
387
+ get legalLinks() {
388
+ return options.legalLinks;
389
+ },
390
+ get noStyle() {
391
+ return options.noStyle;
392
+ },
393
+ get scrollLock() {
394
+ return options.scrollLock;
395
+ },
396
+ get theme() {
397
+ return userTheme;
398
+ },
399
+ get trapFocus() {
400
+ return options.trapFocus;
401
+ },
402
+ });
403
+
404
+ const themeCSS = $derived(userTheme ? generateThemeCSS(userTheme) : '');
405
+
406
+ let themeStyleEl: HTMLStyleElement | null = null;
407
+ let ownedStyleEl = false;
408
+
409
+ $effect(() => {
410
+ if (typeof document === 'undefined') {
411
+ return;
412
+ }
413
+ if (!themeCSS) {
414
+ if (ownedStyleEl && themeStyleEl) {
415
+ themeStyleEl.remove();
416
+ themeStyleEl = null;
417
+ ownedStyleEl = false;
418
+ }
419
+ return;
420
+ }
421
+ if (!themeStyleEl) {
422
+ themeStyleEl = document.getElementById(
423
+ 'c15t-theme'
424
+ ) as HTMLStyleElement | null;
425
+ if (!themeStyleEl) {
426
+ themeStyleEl = document.createElement('style');
427
+ themeStyleEl.id = 'c15t-theme';
428
+ document.head.appendChild(themeStyleEl);
429
+ ownedStyleEl = true;
430
+ }
431
+ }
432
+ // A nonce-based CSP rejects the injected block without it.
433
+ if (options.nonce) {
434
+ themeStyleEl.nonce = options.nonce;
435
+ }
436
+ themeStyleEl.textContent = themeCSS;
437
+ });
438
+
439
+ $effect(() => {
440
+ if (options.colorScheme === null || options.colorScheme === undefined) {
441
+ return;
442
+ }
443
+ return setupColorScheme(options.colorScheme);
444
+ });
445
+
446
+ onDestroy(() => {
447
+ unsubscribe();
448
+ unsubscribeIAB();
449
+ if (ownedStyleEl && themeStyleEl) {
450
+ themeStyleEl.remove();
451
+ themeStyleEl = null;
452
+ ownedStyleEl = false;
453
+ }
454
+ });
455
+ </script>
456
+
457
+ {#if children}
458
+ {@render children()}
459
+ {/if}
@@ -0,0 +1,27 @@
1
+ import type { ConsentRuntime } from '@c15t/core/runtime';
2
+ import type { Snippet } from 'svelte';
3
+ import type { ConsentManagerOptions } from '../types';
4
+ type ProviderOptionsInput = Omit<ConsentManagerOptions, 'mode'> & {
5
+ mode?: ConsentManagerOptions['mode'];
6
+ };
7
+ interface ProviderRuntimeProps {
8
+ children?: Snippet;
9
+ /**
10
+ * An externally owned runtime to render instead of creating one.
11
+ *
12
+ * A SvelteKit root layout or an Astro page can create a single
13
+ * runtime with `createConsentRuntime()` and share it across
14
+ * component trees that cannot see each other's context. The
15
+ * provider neither starts nor disposes a runtime it did not
16
+ * create — the owner does both.
17
+ */
18
+ runtime?: ConsentRuntime;
19
+ }
20
+ type ConsentManagerProviderProps = (ConsentManagerOptions & ProviderRuntimeProps & {
21
+ options?: ProviderOptionsInput;
22
+ }) | (ProviderOptionsInput & ProviderRuntimeProps & {
23
+ options: ConsentManagerOptions;
24
+ });
25
+ declare const ManagerProvider: import("svelte").Component<ConsentManagerProviderProps, {}, "">;
26
+ type ManagerProvider = ReturnType<typeof ManagerProvider>;
27
+ export default ManagerProvider;
@@ -0,0 +1,79 @@
1
+ <script lang="ts">
2
+ import bannerStyles from '@c15t/ui/styles/components/consent-banner';
3
+ import dialogStyles from '@c15t/ui/styles/components/consent-dialog';
4
+ import iabBannerStyles from '@c15t/ui/styles/components/iab-consent-banner';
5
+ import iabDialogStyles from '@c15t/ui/styles/components/iab-consent-dialog';
6
+
7
+ import { getThemeContext } from '../context.svelte';
8
+ import { resolveComponentStyles } from '../utils';
9
+
10
+ let {
11
+ variant = 'banner',
12
+ visible = true,
13
+ }: {
14
+ variant?: 'banner' | 'dialog' | 'iab-banner' | 'iab-dialog';
15
+ visible?: boolean;
16
+ } = $props();
17
+
18
+ const theme = getThemeContext();
19
+
20
+ const styles = $derived.by(() => {
21
+ if (variant === 'dialog') {
22
+ return dialogStyles;
23
+ }
24
+ if (variant === 'iab-dialog') {
25
+ return iabDialogStyles;
26
+ }
27
+ return variant === 'iab-banner' ? iabBannerStyles : bannerStyles;
28
+ });
29
+
30
+ const themeKey = $derived.by(() => {
31
+ if (variant === 'dialog') {
32
+ return 'consentDialogOverlay' as const;
33
+ }
34
+ if (variant === 'iab-dialog') {
35
+ return 'iabConsentDialogOverlay' as const;
36
+ }
37
+ return variant === 'iab-banner'
38
+ ? ('iabConsentBannerOverlay' as const)
39
+ : ('consentBannerOverlay' as const);
40
+ });
41
+
42
+ const testId = $derived.by(() => {
43
+ if (variant === 'dialog') {
44
+ return 'consent-dialog-overlay';
45
+ }
46
+ if (variant === 'iab-dialog') {
47
+ return 'iab-consent-dialog-overlay';
48
+ }
49
+ return variant === 'iab-banner'
50
+ ? 'iab-consent-banner-overlay'
51
+ : 'consent-banner-overlay';
52
+ });
53
+
54
+ const themeStyle = $derived(
55
+ resolveComponentStyles(
56
+ themeKey,
57
+ theme.theme,
58
+ { baseClassName: styles.overlay },
59
+ theme.noStyle
60
+ )
61
+ );
62
+
63
+ const className = $derived(
64
+ theme.noStyle
65
+ ? themeStyle.className || ''
66
+ : `${themeStyle.className || ''} ${visible ? styles.overlayVisible : styles.overlayHidden}`
67
+ );
68
+ </script>
69
+
70
+ <div
71
+ class={className}
72
+ style={themeStyle.style
73
+ ? Object.entries(themeStyle.style)
74
+ .map(([k, v]) => `${k}:${v}`)
75
+ .join(';')
76
+ : undefined}
77
+ data-testid={testId}
78
+ aria-hidden="true"
79
+ ></div>
@@ -0,0 +1,7 @@
1
+ type $$ComponentProps = {
2
+ variant?: 'banner' | 'dialog' | 'iab-banner' | 'iab-dialog';
3
+ visible?: boolean;
4
+ };
5
+ declare const Overlay: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type Overlay = ReturnType<typeof Overlay>;
7
+ export default Overlay;
@@ -0,0 +1,33 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ import { getConsentContext } from '../context.svelte';
5
+ import ConsentButton from './action-button.svelte';
6
+
7
+ const consent = getConsentContext();
8
+
9
+ let {
10
+ children,
11
+ noStyle = true,
12
+ ...restProps
13
+ }: {
14
+ children: Snippet;
15
+ noStyle?: boolean;
16
+ 'data-testid'?: string;
17
+ [key: string]: unknown;
18
+ } = $props();
19
+ </script>
20
+
21
+ {#if consent.state.hasConsentUi}
22
+ <ConsentButton
23
+ action="open-consent-dialog"
24
+ {noStyle}
25
+ data-testid="consent-dialog-link"
26
+ data-c15t-rights={consent.snapshot.policyRule.rights.join(' ')}
27
+ {...restProps}
28
+ >
29
+ {#if children}
30
+ {@render children()}
31
+ {/if}
32
+ </ConsentButton>
33
+ {/if}