@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,514 @@
1
+ <script lang="ts">
2
+ import type {
3
+ LegalLinks as LegalLinksType,
4
+ Model,
5
+ PromptPosition,
6
+ PromptVariant,
7
+ } from '@c15t/core';
8
+ import {
9
+ defaultTranslationConfig,
10
+ resolveConsentPresentation,
11
+ } from '@c15t/core';
12
+ import type { PolicyRight } from '@c15t/schema/types';
13
+ import styles from '@c15t/ui/styles/components/consent-banner';
14
+ import { getTextDirection, resolveTranslations } from '@c15t/ui/utils';
15
+
16
+ import { focusTrap } from '../actions/focus-trap';
17
+ // Banner uses custom portal/focus-trap/scroll-lock actions (not Ark UI's built-in)
18
+ // because the banner is not an Ark Dialog - it's a simpler container that
19
+ // conditionally acts as a dialog. The ConsentDialog uses Ark's Dialog which
20
+ // includes its own focus trap and scroll prevention. When transitioning from
21
+ // banner to dialog, the banner unmounts (removing its focus trap) before
22
+ // the dialog mounts (establishing Ark's focus trap), so they don't compete.
23
+ import { portal } from '../actions/portal';
24
+ import { scrollLock } from '../actions/scroll-lock';
25
+ import { getConsentContext, getThemeContext } from '../context.svelte';
26
+ import { useBannerVisibility } from '../use-banner-visibility.svelte';
27
+ import { resolveComponentStyles, resolveConsentActionStyle } from '../utils';
28
+ import ConsentButton from './action-button.svelte';
29
+ import Branding from './branding.svelte';
30
+ import InlineLegalLinks from './inline-legal-links.svelte';
31
+ import Overlay from './overlay.svelte';
32
+ import PolicyActionsRenderer from './policy-actions-renderer.svelte';
33
+
34
+ /**
35
+ * Button identifiers for the consent banner layout.
36
+ */
37
+ type ConsentBannerButton = 'reject' | 'accept' | 'customize' | 'dismiss';
38
+ type ConsentBannerLayout = (ConsentBannerButton | ConsentBannerButton[])[];
39
+
40
+ let {
41
+ noStyle: localNoStyle,
42
+ disableAnimation: localDisableAnimation,
43
+ scrollLock: localScrollLock,
44
+ trapFocus: localTrapFocus,
45
+ title,
46
+ description,
47
+ rejectButtonText,
48
+ customizeButtonText,
49
+ acceptButtonText,
50
+ dismissButtonText,
51
+ hideBranding = false,
52
+ legalLinks,
53
+ layout,
54
+ primaryButton,
55
+ variant,
56
+ position,
57
+ blocking,
58
+ models = ['opt-in', 'opt-out', 'iab'] as Model[],
59
+ class: className,
60
+ }: {
61
+ noStyle?: boolean;
62
+ disableAnimation?: boolean;
63
+ scrollLock?: boolean;
64
+ trapFocus?: boolean;
65
+ title?: string;
66
+ description?: string;
67
+ rejectButtonText?: string;
68
+ customizeButtonText?: string;
69
+ acceptButtonText?: string;
70
+ dismissButtonText?: string;
71
+ hideBranding?: boolean;
72
+ legalLinks?: (keyof LegalLinksType)[] | null;
73
+ layout?: ConsentBannerLayout;
74
+ primaryButton?: ConsentBannerButton | ConsentBannerButton[];
75
+ /**
76
+ * Shape of the prompt. Overrides the host presentation.
77
+ * A notice defaults to `bar`, a choice prompt to `floating`.
78
+ */
79
+ variant?: PromptVariant;
80
+ /**
81
+ * Where the prompt sits. Must be valid for the resolved variant;
82
+ * an invalid value falls back to the variant default with a
83
+ * development diagnostic.
84
+ */
85
+ position?: PromptPosition;
86
+ /**
87
+ * Backdrop, scroll lock, focus trap, and no dismissal from outside,
88
+ * as one value. `wall` is always blocking; a notice never is.
89
+ */
90
+ blocking?: boolean;
91
+ models?: Model[];
92
+ class?: string;
93
+ } = $props();
94
+
95
+ /** Corner mirror for right-to-left text when the host set no position. */
96
+ const MIRRORED_POSITIONS: Partial<Record<PromptPosition, PromptPosition>> = {
97
+ 'bottom-left': 'bottom-right',
98
+ 'bottom-right': 'bottom-left',
99
+ 'top-left': 'top-right',
100
+ 'top-right': 'top-left',
101
+ };
102
+
103
+ const consent = getConsentContext();
104
+ const theme = getThemeContext();
105
+
106
+ const noStyle = $derived(localNoStyle ?? theme.noStyle ?? false);
107
+ const disableAnimation = $derived(
108
+ localDisableAnimation ?? theme.disableAnimation ?? false
109
+ );
110
+
111
+ // Translations
112
+ const translations = $derived(
113
+ resolveTranslations(
114
+ consent.state.translationConfig,
115
+ defaultTranslationConfig
116
+ )
117
+ );
118
+ const textDirection = $derived(
119
+ getTextDirection(consent.state.translationConfig?.defaultLanguage)
120
+ );
121
+
122
+ // Visibility logic
123
+ const shouldShowBanner = $derived(
124
+ consent.state.hasConsentUi &&
125
+ consent.state.activeUI === 'banner' &&
126
+ consent.snapshot.promptRequirement.kind !== 'none' &&
127
+ models.includes(consent.state.model)
128
+ );
129
+
130
+ const visibility = useBannerVisibility(
131
+ () => shouldShowBanner,
132
+ () => disableAnimation
133
+ );
134
+
135
+ // Styling - per-element theme key resolution shared across framework adapters.
136
+ const rootStyle = $derived(
137
+ resolveComponentStyles(
138
+ 'consentBanner',
139
+ theme.theme,
140
+ {
141
+ baseClassName: styles.root,
142
+ className,
143
+ noStyle,
144
+ },
145
+ noStyle
146
+ )
147
+ );
148
+
149
+ const cardStyle = $derived(
150
+ resolveComponentStyles(
151
+ 'consentBannerCard',
152
+ theme.theme,
153
+ { baseClassName: styles.card, noStyle },
154
+ noStyle
155
+ )
156
+ );
157
+
158
+ const headerStyle = $derived(
159
+ resolveComponentStyles(
160
+ 'consentBannerHeader',
161
+ theme.theme,
162
+ { baseClassName: styles.header, noStyle },
163
+ noStyle
164
+ )
165
+ );
166
+
167
+ const titleStyle = $derived(
168
+ resolveComponentStyles(
169
+ 'consentBannerTitle',
170
+ theme.theme,
171
+ { baseClassName: styles.title, noStyle },
172
+ noStyle
173
+ )
174
+ );
175
+
176
+ const descriptionStyle = $derived(
177
+ resolveComponentStyles(
178
+ 'consentBannerDescription',
179
+ theme.theme,
180
+ { baseClassName: styles.description, noStyle },
181
+ noStyle
182
+ )
183
+ );
184
+
185
+ const footerStyle = $derived(
186
+ resolveComponentStyles(
187
+ 'consentBannerFooter',
188
+ theme.theme,
189
+ { baseClassName: styles.footer, noStyle },
190
+ noStyle
191
+ )
192
+ );
193
+
194
+ const footerSubGroupStyle = $derived(
195
+ resolveComponentStyles(
196
+ 'consentBannerFooterSubGroup',
197
+ theme.theme,
198
+ { noStyle },
199
+ noStyle
200
+ )
201
+ );
202
+
203
+ const rightsStyle = $derived(
204
+ resolveComponentStyles(
205
+ 'consentBannerRights',
206
+ theme.theme,
207
+ { baseClassName: styles.rights, noStyle },
208
+ noStyle
209
+ )
210
+ );
211
+
212
+ const rightLinkStyle = $derived(
213
+ resolveComponentStyles(
214
+ 'consentBannerRightLink',
215
+ theme.theme,
216
+ { baseClassName: styles.rightLink, noStyle },
217
+ noStyle
218
+ )
219
+ );
220
+
221
+ // A right control opens the preference center without recording a choice.
222
+ const openPreferences = function openPreferences() {
223
+ consent.state.setActiveUI('dialog');
224
+ };
225
+
226
+ const finalClassName = $derived(
227
+ noStyle
228
+ ? rootStyle.className || ''
229
+ : `${rootStyle.className || ''} ${visibility.isVisible ? styles.bannerVisible : styles.bannerHidden}`
230
+ );
231
+
232
+ const localPrimaryActions = $derived.by(() => {
233
+ if (primaryButton === undefined) {
234
+ return undefined;
235
+ }
236
+ return Array.isArray(primaryButton) ? primaryButton : [primaryButton];
237
+ });
238
+ // Button helpers
239
+ const themedActions = $derived({
240
+ accept: resolveConsentActionStyle(theme.theme, 'accept'),
241
+ customize: resolveConsentActionStyle(theme.theme, 'customize'),
242
+ dismiss: resolveConsentActionStyle(theme.theme, 'dismiss'),
243
+ reject: resolveConsentActionStyle(theme.theme, 'reject'),
244
+ save: resolveConsentActionStyle(theme.theme, 'save'),
245
+ });
246
+ const presentation = $derived(
247
+ resolveConsentPresentation({
248
+ actionAppearance: themedActions,
249
+ override: {
250
+ blocking,
251
+ layout,
252
+ position,
253
+ primaryActions: localPrimaryActions,
254
+ scrollLock: localScrollLock,
255
+ trapFocus: localTrapFocus,
256
+ variant,
257
+ },
258
+ policy: consent.snapshot.policyRule,
259
+ presentation: {
260
+ ...consent.state.presentation,
261
+ prompt: {
262
+ scrollLock: theme.scrollLock,
263
+ trapFocus: theme.trapFocus,
264
+ ...consent.state.presentation?.prompt,
265
+ },
266
+ },
267
+ surface: 'prompt',
268
+ })
269
+ );
270
+ const actionGroups = $derived(presentation.actionGroups);
271
+ const primaryActions = $derived(presentation.primaryActions);
272
+ // Additional buttons that open preferences, styled as underlined text.
273
+ const preferenceControls = $derived(presentation.preferenceControls);
274
+ // A notice offers dismiss alone; with no primary resolved it takes the lead.
275
+ const dismissIsPrimary = $derived(
276
+ primaryActions.length === 0 &&
277
+ presentation.orderedActions.length === 1 &&
278
+ presentation.orderedActions[0] === 'dismiss'
279
+ );
280
+ const promptKind = $derived(consent.snapshot.policyRule.prompt);
281
+ const isNotice = $derived(promptKind === 'notice');
282
+ const direction = $derived(presentation.direction);
283
+ const shouldFillActions = $derived(presentation.shouldFillActions);
284
+ const shouldTrapFocus = $derived(presentation.trapFocus);
285
+ const shouldScrollLock = $derived(presentation.scrollLock);
286
+ const resolvedVariant = $derived(presentation.variant);
287
+ const isBlocking = $derived(presentation.blocking);
288
+ // A defaulted corner follows the text direction; a host position is
289
+ // physical and stays put.
290
+ const resolvedPosition = $derived.by(() => {
291
+ const { position: resolved, positionSource } = presentation;
292
+ const corner =
293
+ resolvedVariant === 'floating' || resolvedVariant === 'widget';
294
+ if (textDirection === 'rtl' && positionSource === 'default' && corner) {
295
+ return MIRRORED_POSITIONS[resolved] ?? resolved;
296
+ }
297
+ return resolved;
298
+ });
299
+
300
+ $effect(() => {
301
+ if (
302
+ typeof process === 'undefined' ||
303
+ process.env?.NODE_ENV !== 'production'
304
+ ) {
305
+ for (const diagnostic of presentation.diagnostics) {
306
+ console.warn(diagnostic.message);
307
+ }
308
+ }
309
+ });
310
+
311
+ // Resolved texts. A notice has its own copy: it informs and points at
312
+ // the opt-out instead of asking for permission.
313
+ const englishTranslations = defaultTranslationConfig.translations.en;
314
+ const resolvedTitle = $derived(
315
+ title ??
316
+ (isNotice
317
+ ? (translations.cookieBanner.noticeTitle ??
318
+ englishTranslations?.cookieBanner?.noticeTitle)
319
+ : translations.cookieBanner.title)
320
+ );
321
+ const resolvedDescription = $derived(
322
+ description ??
323
+ (isNotice
324
+ ? (translations.cookieBanner.noticeDescription ??
325
+ englishTranslations?.cookieBanner?.noticeDescription)
326
+ : translations.cookieBanner.description)
327
+ );
328
+ // Acknowledgement records no category choice.
329
+ const resolvedDismissText = $derived(
330
+ dismissButtonText ??
331
+ translations.common.acknowledge ??
332
+ translations.common.dismiss ??
333
+ englishTranslations?.common?.acknowledge
334
+ );
335
+ const rightLabels = $derived<
336
+ Partial<Record<PolicyRight, string | undefined>>
337
+ >({
338
+ 'opt-out':
339
+ translations.rights?.optOut ?? englishTranslations?.rights?.optOut,
340
+ preferences:
341
+ translations.rights?.preferences ??
342
+ englishTranslations?.rights?.preferences,
343
+ });
344
+ const resolvedRejectText = $derived(
345
+ rejectButtonText ?? translations.common.rejectAll
346
+ );
347
+ const resolvedAcceptText = $derived(
348
+ acceptButtonText ?? translations.common.acceptAll
349
+ );
350
+ const resolvedCustomizeText = $derived(
351
+ customizeButtonText ?? translations.common.customize
352
+ );
353
+ </script>
354
+
355
+ {#if visibility.isMounted && visibility.shouldRender}
356
+ <div use:portal>
357
+ {#if shouldScrollLock}
358
+ <Overlay visible={visibility.isVisible} />
359
+ {/if}
360
+ <div
361
+ bind:this={visibility.bannerEl}
362
+ class={finalClassName}
363
+ dir={textDirection}
364
+ data-variant={resolvedVariant}
365
+ data-position={resolvedPosition}
366
+ data-blocking={isBlocking ? 'true' : undefined}
367
+ data-prompt={promptKind}
368
+ data-model={consent.state.model}
369
+ data-testid="consent-banner-root"
370
+ use:scrollLock={shouldScrollLock}
371
+ >
372
+ <div class={noStyle ? '' : styles.cardShell || ''}>
373
+ <Branding
374
+ {hideBranding}
375
+ {noStyle}
376
+ variant="banner-tag"
377
+ themeKey="consentBannerTag"
378
+ data-testid="consent-banner-branding"
379
+ />
380
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
381
+ <div
382
+ class={noStyle ? '' : cardStyle.className || ''}
383
+ data-testid="consent-banner-card"
384
+ tabindex="-1"
385
+ role={isBlocking ? 'dialog' : 'region'}
386
+ aria-modal={isBlocking ? 'true' : undefined}
387
+ aria-label={resolvedTitle}
388
+ use:focusTrap={shouldTrapFocus}
389
+ >
390
+ <div
391
+ class={noStyle ? '' : headerStyle.className || ''}
392
+ data-testid="consent-banner-header"
393
+ >
394
+ <h2
395
+ class={noStyle ? '' : titleStyle.className || ''}
396
+ data-testid="consent-banner-title"
397
+ >
398
+ {resolvedTitle}
399
+ </h2>
400
+ <div
401
+ class={noStyle ? '' : descriptionStyle.className || ''}
402
+ data-context="banner"
403
+ data-testid="consent-banner-description"
404
+ >
405
+ {resolvedDescription}
406
+ <InlineLegalLinks
407
+ links={legalLinks}
408
+ themeKey="consentBannerDescription"
409
+ testIdPrefix="consent-banner-legal-link"
410
+ />
411
+ </div>
412
+ </div>
413
+ <PolicyActionsRenderer
414
+ {actionGroups}
415
+ {primaryActions}
416
+ {shouldFillActions}
417
+ {direction}
418
+ {noStyle}
419
+ footerClassName={noStyle ? '' : footerStyle.className || ''}
420
+ footerSubGroupClassName={noStyle
421
+ ? ''
422
+ : footerSubGroupStyle.className || ''}
423
+ footerTestId="consent-banner-footer"
424
+ footerSubGroupTestId="consent-banner-footer-sub-group"
425
+ >
426
+ {#snippet leading()}
427
+ {#if preferenceControls.length > 0}
428
+ <div
429
+ class={noStyle ? '' : rightsStyle.className || ''}
430
+ style={rightsStyle.style
431
+ ? Object.entries(rightsStyle.style)
432
+ .map(([key, value]) => `${key}:${value}`)
433
+ .join(';')
434
+ : undefined}
435
+ data-testid="consent-banner-rights"
436
+ >
437
+ {#each preferenceControls as right (right)}
438
+ <button
439
+ type="button"
440
+ class={noStyle ? '' : rightLinkStyle.className || ''}
441
+ style={rightLinkStyle.style
442
+ ? Object.entries(rightLinkStyle.style)
443
+ .map(([key, value]) => `${key}:${value}`)
444
+ .join(';')
445
+ : undefined}
446
+ data-action="right"
447
+ data-right={right}
448
+ data-c15t-rights={consent.snapshot.policyRule.rights.join(
449
+ ' '
450
+ )}
451
+ data-testid={`consent-banner-right-link-${right}`}
452
+ onclick={openPreferences}
453
+ >
454
+ {rightLabels[right] ?? right}
455
+ </button>
456
+ {/each}
457
+ </div>
458
+ {/if}
459
+ {/snippet}
460
+ {#snippet renderAction(action: string, isPrimary: boolean)}
461
+ {#if action === 'reject'}
462
+ <ConsentButton
463
+ action="reject-consent"
464
+ variant={themedActions.reject.variant ??
465
+ (isPrimary ? 'primary' : 'neutral')}
466
+ mode={themedActions.reject.mode ?? 'stroke'}
467
+ closeConsentBanner
468
+ data-action="reject"
469
+ data-testid="consent-banner-reject-button"
470
+ >
471
+ {resolvedRejectText}
472
+ </ConsentButton>
473
+ {:else if action === 'accept'}
474
+ <ConsentButton
475
+ action="accept-consent"
476
+ variant={themedActions.accept.variant ??
477
+ (isPrimary ? 'primary' : 'neutral')}
478
+ mode={themedActions.accept.mode ?? 'stroke'}
479
+ closeConsentBanner
480
+ data-action="accept"
481
+ data-testid="consent-banner-accept-button"
482
+ >
483
+ {resolvedAcceptText}
484
+ </ConsentButton>
485
+ {:else if action === 'dismiss'}
486
+ <ConsentButton
487
+ action="dismiss-notice"
488
+ variant={themedActions.dismiss.variant ??
489
+ (isPrimary || dismissIsPrimary ? 'primary' : 'neutral')}
490
+ mode={themedActions.dismiss.mode ?? 'stroke'}
491
+ data-action="dismiss"
492
+ data-testid="consent-banner-dismiss-button"
493
+ >
494
+ {resolvedDismissText}
495
+ </ConsentButton>
496
+ {:else if action === 'customize'}
497
+ <ConsentButton
498
+ action="open-consent-dialog"
499
+ variant={themedActions.customize.variant ??
500
+ (isPrimary ? 'primary' : 'neutral')}
501
+ mode={themedActions.customize.mode ?? 'stroke'}
502
+ data-action="customize"
503
+ data-testid="consent-banner-customize-button"
504
+ >
505
+ {resolvedCustomizeText}
506
+ </ConsentButton>
507
+ {/if}
508
+ {/snippet}
509
+ </PolicyActionsRenderer>
510
+ </div>
511
+ </div>
512
+ </div>
513
+ </div>
514
+ {/if}
@@ -0,0 +1,43 @@
1
+ import type { LegalLinks as LegalLinksType, Model, PromptPosition, PromptVariant } from '@c15t/core';
2
+ /**
3
+ * Button identifiers for the consent banner layout.
4
+ */
5
+ type ConsentBannerButton = 'reject' | 'accept' | 'customize' | 'dismiss';
6
+ type ConsentBannerLayout = (ConsentBannerButton | ConsentBannerButton[])[];
7
+ type $$ComponentProps = {
8
+ noStyle?: boolean;
9
+ disableAnimation?: boolean;
10
+ scrollLock?: boolean;
11
+ trapFocus?: boolean;
12
+ title?: string;
13
+ description?: string;
14
+ rejectButtonText?: string;
15
+ customizeButtonText?: string;
16
+ acceptButtonText?: string;
17
+ dismissButtonText?: string;
18
+ hideBranding?: boolean;
19
+ legalLinks?: (keyof LegalLinksType)[] | null;
20
+ layout?: ConsentBannerLayout;
21
+ primaryButton?: ConsentBannerButton | ConsentBannerButton[];
22
+ /**
23
+ * Shape of the prompt. Overrides the host presentation.
24
+ * A notice defaults to `bar`, a choice prompt to `floating`.
25
+ */
26
+ variant?: PromptVariant;
27
+ /**
28
+ * Where the prompt sits. Must be valid for the resolved variant;
29
+ * an invalid value falls back to the variant default with a
30
+ * development diagnostic.
31
+ */
32
+ position?: PromptPosition;
33
+ /**
34
+ * Backdrop, scroll lock, focus trap, and no dismissal from outside,
35
+ * as one value. `wall` is always blocking; a notice never is.
36
+ */
37
+ blocking?: boolean;
38
+ models?: Model[];
39
+ class?: string;
40
+ };
41
+ declare const Prompt: import("svelte").Component<$$ComponentProps, {}, "">;
42
+ type Prompt = ReturnType<typeof Prompt>;
43
+ export default Prompt;
@@ -0,0 +1,125 @@
1
+ import type { ActiveUI, AllConsentNames, ConsentKernel, ConsentPresentation, ResolvedConsentPresentation, ConsentSnapshot, ConsentState, ConsentType, HasCondition, KernelIABState, Model, TranslationConfig } from '@c15t/core';
2
+ import type { Theme, UIOptions } from '@c15t/ui/theme';
3
+ import type { ConsentManagerOptions } from './types';
4
+ export type SaveType = 'all' | 'custom' | 'necessary';
5
+ export interface SvelteIABState extends KernelIABState {
6
+ config: {
7
+ enabled: boolean;
8
+ cmpId: number | null;
9
+ };
10
+ isLoadingGVL: boolean;
11
+ nonIABVendors: KernelIABState['customVendors'];
12
+ preferenceCenterTab: 'purposes' | 'vendors';
13
+ setPreferenceCenterTab: (tab: 'purposes' | 'vendors') => void;
14
+ setVendorConsent: (vendorId: string | number, value: boolean) => void;
15
+ setVendorLegitimateInterest: (vendorId: string | number, value: boolean) => void;
16
+ setPurposeConsent: (purposeId: number, value: boolean) => void;
17
+ setPurposeLegitimateInterest: (purposeId: number, value: boolean) => void;
18
+ setSpecialFeatureOptIn: (featureId: number, value: boolean) => void;
19
+ acceptAll: () => void;
20
+ rejectAll: () => void;
21
+ save: () => Promise<void>;
22
+ }
23
+ export interface ConsentDraftState {
24
+ readonly values: Partial<ConsentState>;
25
+ readonly isStale: boolean;
26
+ set: (name: AllConsentNames, value: boolean) => void;
27
+ reset: () => void;
28
+ save: (categories: readonly AllConsentNames[]) => Promise<void>;
29
+ }
30
+ export interface ConsentManagerState extends Pick<ConsentSnapshot, 'explicitChoice' | 'effectivePermissions' | 'promptRequirement' | 'noticeDismissal' | 'privacySignals' | 'optOutDirectives' | 'resolution' | 'policyRule' | 'restrictions' | 'nextDeadline' | 'subject' | 'evaluatedAt' | 'evaluationPolicy' | 'policyPending' | 'location' | 'overrides' | 'revision' | 'translations' | 'user'> {
31
+ activeUI: ActiveUI;
32
+ branding: NonNullable<ConsentSnapshot['branding']>;
33
+ selectedConsents: Partial<ConsentState>;
34
+ selectedConsentTypes: Partial<ConsentState>;
35
+ presentation?: ConsentPresentation;
36
+ readonly draft: ConsentDraftState;
37
+ consentCategories: AllConsentNames[];
38
+ consentTypes: ConsentType[];
39
+ iab: SvelteIABState | null;
40
+ manager: null;
41
+ model: Model;
42
+ /**
43
+ * Whether a policy rule is resolved for this visitor. Every c15t consent
44
+ * surface renders nothing until one is: an unconfigured, failed, or
45
+ * unmatched resolution leaves nothing to consent to, and the surfaces
46
+ * appear on their own once a later init supplies a rule.
47
+ */
48
+ hasPolicy: boolean;
49
+ /**
50
+ * Whether the resolved rule owes any consent UI. A prompt owes a banner
51
+ * and a preference center; rights owe a way back to preferences. A `none`
52
+ * rule with no rights owes neither, so every surface stays hidden while
53
+ * the permissions it grants apply. `false` until a rule is resolved.
54
+ */
55
+ hasConsentUi: boolean;
56
+ legalLinks: ConsentManagerOptions['legalLinks'];
57
+ translationConfig: TranslationConfig;
58
+ getDisplayedConsents: () => ConsentType[];
59
+ has: (condition: HasCondition<AllConsentNames>) => boolean;
60
+ dismissNotice: () => Promise<unknown>;
61
+ saveConsents: (type: SaveType) => Promise<void>;
62
+ setActiveUI: (ui: ActiveUI, options?: {
63
+ force?: boolean;
64
+ }) => void;
65
+ setConsent: (name: AllConsentNames, value: boolean) => void;
66
+ setLanguage: (code: string) => void;
67
+ setSelectedConsent: (name: AllConsentNames, value: boolean) => void;
68
+ subscribeToConsentChanges: (listener: (state: ConsentState) => void) => () => void;
69
+ }
70
+ export interface ConsentContextValue {
71
+ readonly clearRecords: () => void;
72
+ readonly kernel: ConsentKernel;
73
+ readonly snapshot: ConsentSnapshot;
74
+ readonly state: ConsentManagerState;
75
+ readonly manager: ConsentKernel;
76
+ }
77
+ export interface ThemeContextValue {
78
+ readonly theme?: Theme;
79
+ readonly noStyle?: boolean;
80
+ readonly disableAnimation?: boolean;
81
+ readonly scrollLock?: boolean;
82
+ readonly trapFocus?: boolean;
83
+ readonly colorScheme?: UIOptions['colorScheme'];
84
+ readonly legalLinks?: ConsentManagerOptions['legalLinks'];
85
+ }
86
+ export interface ConsentControllerOptions {
87
+ clearRecords?: () => void;
88
+ getSnapshot: () => ConsentSnapshot;
89
+ getDraft: () => ConsentDraftState;
90
+ getIAB: () => SvelteIABState | null;
91
+ getConsentCategories: () => AllConsentNames[];
92
+ getLegalLinks: () => ConsentManagerOptions['legalLinks'];
93
+ getPresentation: () => ConsentPresentation | undefined;
94
+ }
95
+ export declare const setConsentContext: (kernel: ConsentKernel, options: ConsentControllerOptions) => void;
96
+ export declare const getConsentContext: () => ConsentContextValue;
97
+ export declare const getConsentKernel: () => ConsentKernel;
98
+ export declare const getSnapshot: () => ConsentSnapshot;
99
+ /**
100
+ * Returns the reactive consent manager controller for the current component.
101
+ *
102
+ * Exposes both readable state (`consents`, `activeUI`, `model`, …) and
103
+ * mutators (`setConsent`, `saveConsents`, `setActiveUI`, `setLanguage`, …).
104
+ * This is the primary API for reading and writing consent from inside your
105
+ * own components — equivalent to React's `useConsentManager()`.
106
+ *
107
+ * Must be called inside a component tree wrapped in `<ConsentManagerProvider>`.
108
+ */
109
+ export declare const getConsentManager: () => ConsentManagerState;
110
+ export interface HeadlessConsentSurfaceState extends ResolvedConsentPresentation {
111
+ isVisible: boolean;
112
+ }
113
+ export declare const getHeadlessConsent: () => {
114
+ readonly activeUI: ActiveUI;
115
+ readonly banner: HeadlessConsentSurfaceState;
116
+ closeUI(): void;
117
+ readonly dialog: HeadlessConsentSurfaceState;
118
+ openBanner(): void;
119
+ openDialog(): void;
120
+ performAction(action: "accept" | "reject" | "customize" | "dismiss" | "save"): Promise<void>;
121
+ saveCustomPreferences(): Promise<void>;
122
+ };
123
+ export declare const getIAB: () => SvelteIABState | null;
124
+ export declare const setThemeContext: (value: ThemeContextValue) => void;
125
+ export declare const getThemeContext: () => ThemeContextValue;