@consenti/ui 0.2.0 → 0.3.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.
package/dist/vue.d.ts CHANGED
@@ -1,11 +1,20 @@
1
1
  import * as vue from 'vue';
2
2
 
3
3
  type NonEmptyArray<T> = [T, ...T[]];
4
+ /**
5
+ * Deep-partial with JSON Merge Patch (RFC 7396) delete semantics: any key may be
6
+ * set to `null` instead of its usual type to signal "delete this key" when the
7
+ * partial is applied with `deepMerge()` — `undefined`/omitted keeps the base
8
+ * value, `null` removes it. Only meaningful for object/map keys (e.g. removing
9
+ * a `CategoryMap`/`CookieMap` entry via `profileOverride`); array elements are
10
+ * merged positionally and don't support deletion this way.
11
+ */
4
12
  type DeepPartial<T> = T extends Function ? T : T extends Array<infer InferredArrayType> ? Array<DeepPartial<InferredArrayType>> : T extends ReadonlyArray<infer InferredReadonlyArrayType> ? ReadonlyArray<DeepPartial<InferredReadonlyArrayType>> : T extends object ? {
5
- [Key in keyof T]?: DeepPartial<T[Key]>;
13
+ [Key in keyof T]?: DeepPartial<T[Key]> | null;
6
14
  } : T;
7
15
 
8
16
  declare const COMPLIANCE_GROUP_IDS: readonly ["opt-in", "opt-out", "opt-out-strict", "opt-in-dpdpa", "opt-in-china", "opt-in-brazil", "general-privacy-consent", "notice-only"];
17
+ declare const COOKIE_PURPOSE_IDS: readonly ["necessary", "functional", "preferences", "analytics", "marketing"];
9
18
 
10
19
  /**
11
20
  * Compliance-related literal types, derived from the regulation/group ID data
@@ -21,6 +30,13 @@ declare const COMPLIANCE_GROUP_IDS: readonly ["opt-in", "opt-out", "opt-out-stri
21
30
  */
22
31
 
23
32
  type ComplianceGroupId = typeof COMPLIANCE_GROUP_IDS[number];
33
+ /**
34
+ * `(string & {})` (rather than plain `string`) keeps IDE autocomplete for the
35
+ * known `ComplianceGroupId` literals while still allowing any other string —
36
+ * used to target a profile authored against a custom (non-built-in)
37
+ * `customComplianceGroup` instead of one of the 8 built-in groups.
38
+ */
39
+ type ComplianceType = 'auto' | ComplianceGroupId | (string & {}) | Symbol;
24
40
 
25
41
  interface ComplianceMapData {
26
42
  version: string;
@@ -35,24 +51,67 @@ interface CountryComplianceEntry {
35
51
  description: string;
36
52
  }>;
37
53
  }
54
+ interface ConsentDbRecord {
55
+ id: string;
56
+ tenantId: string;
57
+ visitorId: string;
58
+ profileId: string;
59
+ locale: string;
60
+ consentJson: ConsentValue;
61
+ gpcDetected: boolean;
62
+ source: 'banner' | 'api' | 'import';
63
+ ageVerified?: boolean;
64
+ parentalConsentToken?: string;
65
+ tcfString?: string;
66
+ /** HMAC-SHA256 signature over the record's core fields, hex-encoded. Only present when
67
+ * `consentSigningKey` is configured — opt-in tamper-evidence for server-stored records,
68
+ * independent of the widget's own cookie-signing (`core.cookieSigningKey`). */
69
+ signature?: string;
70
+ createdAt: string;
71
+ updatedAt: string;
72
+ }
38
73
 
39
74
  type ConsentStatus = 'granted' | 'denied' | 'objected';
40
75
  type ConsentAction = 'accept_all' | 'reject_all' | 'custom' | 'update';
41
76
  type ConsentValue = Record<string, ConsentStatus>;
77
+ /**
78
+ * Output format for getConsent(type).
79
+ * 'default' → raw ConsentValue (existing behaviour)
80
+ * 'google-gtm' → Google Consent Mode v2 keys + ads_data_redaction, url_passthrough
81
+ * 'category' → consent per CookiePurpose category
82
+ * 'adobe' → { analytics, target, manager, optimizer }
83
+ * 'meta' → { pixel, api, plugins, facebookLogin }
84
+ * 'microsoft-clarity' → { session, heatmaps, performance }
85
+ * 'twilio-segment' → { identify, page, track, group, alias }
86
+ */
87
+ type ConsentType = 'default' | 'google-gtm' | 'category' | 'adobe' | 'meta' | 'microsoft-clarity' | 'twilio-segment';
88
+ type GpcMode = 'ignore' | 'honor' | 'strict';
89
+ type LegalBasis = 'mandatory' | 'consent' | 'legitimate_interest';
90
+ type CookiePurpose = typeof COOKIE_PURPOSE_IDS[number];
91
+ /**
92
+ * A single consent parameter (tracker/cookie definition).
93
+ * Keyed by its own id inside `Record<string, Cookie>` — the map key IS the id,
94
+ * there is no `id` field on the value itself.
95
+ */
42
96
  interface Cookie {
43
- id: string;
44
- legalBasis?: 'mandatory' | 'consent' | 'legitimate_interest';
45
- /** @deprecated use legalBasis: 'mandatory' */
46
- mandatory?: boolean;
47
- /** @deprecated use legalBasis */
48
- type?: 'consent' | 'legitimate_interest';
97
+ purpose?: CookiePurpose;
49
98
  listenGpc?: boolean;
50
- expiry?: number;
99
+ /**
100
+ * Pre-grant this parameter's default consent to 'granted' (instead of the
101
+ * compliance-group-driven default) when no stored decision exists yet.
102
+ * Only meaningful when the owning category's `legalBasis === 'consent'` —
103
+ * `mandatory`/`legitimate_interest` categories are already effectively
104
+ * pre-granted regardless of this flag. Never overrides an active GPC signal.
105
+ * Default: false.
106
+ */
107
+ preGrant?: boolean;
51
108
  tcfVendorId?: number;
52
109
  tcfPurposes?: number[];
53
110
  tcfSpecialFeatures?: number[];
54
111
  cpraCategory?: 'sale' | 'sharing' | 'sensitive';
55
112
  }
113
+ /** `Cookie` keyed by its own id — the standard shape used everywhere a parameter list is passed around. */
114
+ type CookieMap = Record<string, Cookie>;
56
115
  type ButtonStyle = 'primary' | 'secondary' | 'text' | 'accent';
57
116
  type ButtonAction = 'submit' | 'manage' | 'close' | 'custom' | 'link';
58
117
  interface Button {
@@ -61,8 +120,11 @@ interface Button {
61
120
  action: ButtonAction;
62
121
  cookies?: string[] | '*' | '!';
63
122
  url?: string;
64
- type?: string;
65
123
  }
124
+ /** `Button` keyed by its own id — e.g. `'accept-all'`. The map key IS the id, rendered as the DOM
125
+ * `id` (`consenti-btn-{id}`) so integrators can target specific buttons, and is what
126
+ * `profileOverride`'s `deepMerge` deletes by (`{ 'reject-optional': null }` removes one button). */
127
+ type ButtonMap = Record<string, Button>;
66
128
  interface MainBanner {
67
129
  position: 'top' | 'bottom' | 'middle' | 'left-bottom' | 'right-bottom';
68
130
  overlayOpacity?: number;
@@ -71,29 +133,45 @@ interface MainBanner {
71
133
  heading?: string;
72
134
  headingTag?: string;
73
135
  htmlText: string;
74
- buttons: Button[];
136
+ buttons: ButtonMap;
137
+ /** Stack buttons vertically below this viewport width in px. 0 or undefined = disabled. Default: 576. */
138
+ stackButtonsOnBreakpoint?: number;
139
+ /** Trap keyboard Tab focus within the consent root while this banner is visible. */
140
+ trapFocus?: boolean;
75
141
  }
76
142
  type GpcBanner = MainBanner;
143
+ /**
144
+ * A consent category — the single source of legal basis for every parameter
145
+ * it lists in `cookies`. Keyed by its own id inside `Record<string, Category>` —
146
+ * the map key IS the id, there is no `id` field on the value itself.
147
+ *
148
+ * A parameter must belong to exactly one category (enforced at authoring time,
149
+ * not by this type) — that relationship is what `getCookieLegalBasis`/
150
+ * `isMandatoryCookie` (`@consenti/utils`) resolve at runtime.
151
+ */
77
152
  interface Category {
78
- id: string;
79
153
  heading: string;
80
154
  headingTag?: string;
81
155
  htmlText: string;
82
- mandatory?: boolean;
83
- type?: 'consent' | 'legitimate_interest';
84
- legitimateInterest?: {
85
- enabled?: boolean;
86
- description?: string;
87
- };
156
+ legalBasis: LegalBasis;
157
+ /** Only meaningful when legalBasis === 'legitimate_interest' — the GDPR balancing-test justification shown to visitors. */
158
+ legitimateInterestDescription?: string;
159
+ /** IDs of the parameters (from the profile's `cookies` map) that belong to this category. */
88
160
  cookies: string[];
89
161
  }
162
+ /** `Category` keyed by its own id — the standard shape used everywhere a category list is passed around. */
163
+ type CategoryMap = Record<string, Category>;
90
164
  interface PreferenceModal extends Omit<MainBanner, "position"> {
91
165
  position?: 'left' | 'right' | 'center';
92
166
  subheading?: string;
93
- categories: Category[];
167
+ categories: CategoryMap;
94
168
  persistent?: boolean;
95
169
  /** Screen width in px below which the modal expands to full screen. Default: 576. Set to 0 to disable. */
96
170
  mobileFullScreenBreakpoint?: number;
171
+ /** Label for the optional consent-receipt download checkbox (shown when `allowReceipt` is true). Falls back to a default when omitted. */
172
+ receiptLabel?: string;
173
+ /** Description shown beneath the consent-receipt checkbox. Falls back to a default when omitted. */
174
+ receiptDescription?: string;
97
175
  }
98
176
  interface DpdpaConfig {
99
177
  dataFiduciary: string;
@@ -101,21 +179,30 @@ interface DpdpaConfig {
101
179
  purposeDescription?: string;
102
180
  }
103
181
  interface ResolvedProfile {
104
- id: number;
105
- profileUuid?: string;
106
- version: number;
182
+ id: string;
183
+ /** Incremented in place on every save of this profile. Absent for embedded/local profiles that have no server-side version counter. */
184
+ version?: number;
107
185
  defaultLocale: string;
108
186
  locales?: string[];
109
- cookies: Cookie[];
187
+ cookies: CookieMap;
188
+ /** Days until consent expires and the visitor is asked again (profile-wide). Default: 365. */
189
+ expiryDays?: number;
190
+ allowReceipt?: boolean;
110
191
  mainBanner: MainBanner;
111
192
  gpcBanner?: GpcBanner;
112
193
  preferenceModal: PreferenceModal;
113
194
  cookieSigningKey?: string;
114
195
  dpdpa?: DpdpaConfig;
115
196
  darkMode?: boolean;
116
- gpcMode?: 'ignore' | 'honor' | 'strict';
117
- complianceGroup?: string;
197
+ gpcMode?: GpcMode;
198
+ complianceGroup?: ComplianceGroupId;
118
199
  complianceConfig?: Record<string, string>;
200
+ /** Falls back to `true` (hidden) if neither the widget config nor the profile set it. */
201
+ hidePoweredBy?: boolean;
202
+ /** Show metadata footer strip (Consent ID, Date, Version, Privacy Settings link). */
203
+ showFooterMetadata?: boolean;
204
+ /** Apply WCAG 2.1 AA accessibility enhancements to the widget. */
205
+ enhanceAccessibility?: boolean;
119
206
  }
120
207
  type WidgetCountryResolverFn = () => Promise<{
121
208
  country: string | null;
@@ -127,12 +214,20 @@ interface AgeGateWidgetConfig {
127
214
  minimumAge: number;
128
215
  requireParentalConsent?: boolean;
129
216
  }
217
+ /** Mirrors the server-side `TcfConfig` shape (`@consenti/api`) — `cmpId`/`cmpVersion` must match
218
+ * what the backend uses to encode `tcfString`, since both feed the same simplified TC string. */
219
+ interface TcfWidgetConfig {
220
+ enabled: boolean;
221
+ cmpId: number;
222
+ cmpVersion: number;
223
+ }
130
224
  interface ComplianceWidgetConfig {
131
- type?: 'auto' | ComplianceGroupId | string;
225
+ type?: ComplianceType;
132
226
  geoDataProvider?: 'default' | WidgetCountryResolverFn;
133
227
  autoComplianceMap?: 'default' | ComplianceMapData | 'auto';
134
228
  complianceMapUrl?: string;
135
229
  ageGate?: AgeGateWidgetConfig;
230
+ tcf?: TcfWidgetConfig;
136
231
  }
137
232
  interface GtmConfig {
138
233
  containerId?: string;
@@ -140,6 +235,7 @@ interface GtmConfig {
140
235
  dataLayer?: string;
141
236
  urlPassthrough?: boolean;
142
237
  adsDataRedaction?: boolean;
238
+ verbose?: boolean;
143
239
  }
144
240
  interface UtilsConfig {
145
241
  gtm?: GtmConfig;
@@ -149,7 +245,7 @@ interface ApiConfig {
149
245
  baseUrl?: string;
150
246
  authToken?: string;
151
247
  tenantId?: string;
152
- complianceGroup?: string;
248
+ complianceGroup?: ComplianceGroupId;
153
249
  trustDomain?: boolean;
154
250
  }
155
251
  interface ThemeConfig {
@@ -173,8 +269,10 @@ interface ThemeConfig {
173
269
  }
174
270
  interface CoreConfig {
175
271
  tenantId?: string;
176
- autoHonorGPC?: boolean | 'strict';
177
272
  locale?: string;
273
+ /** Text direction for the banner/modal root. `'auto'` (default) derives it from `locale`
274
+ * (Arabic, Hebrew, Persian, Urdu, ... → `rtl`); set explicitly to override. */
275
+ dir?: 'ltr' | 'rtl' | 'auto';
178
276
  disableCssTemplate?: boolean;
179
277
  cookieSigningKey?: string;
180
278
  allowReceipt?: boolean;
@@ -187,7 +285,11 @@ interface CoreConfig {
187
285
  console?: Array<'info' | 'log' | 'warning' | 'error'>;
188
286
  }
189
287
  interface ConsentiConfig {
190
- core: CoreConfig;
288
+ verbose?: boolean;
289
+ /** Optional — every field of `CoreConfig` is itself optional, so `new ConsentiSetup({})` and
290
+ * `new ConsentiSetup({ compliance: { type: 'opt-in' } })` (omitting `core` entirely) both work,
291
+ * matching the widget's own "Minimal config" documentation. */
292
+ core?: CoreConfig;
191
293
  compliance?: ComplianceWidgetConfig;
192
294
  rootEl?: string | HTMLElement;
193
295
  darkMode?: boolean;
@@ -199,10 +301,12 @@ interface ConsentiConfig {
199
301
  /** When true, suppresses the "Powered by Consenti" footer link in the banner and modal. */
200
302
  hidePoweredBy?: boolean;
201
303
  }
202
- type ConsentiEventName = 'bannerInitialized' | 'consenti:bannerInitialized' | 'bannerVisibility' | 'consenti:bannerVisibility' | 'modalVisibility' | 'consenti:modalVisibility' | 'consentBeingSubmitted' | 'consenti:consentBeingSubmitted' | 'consentSubmitted' | 'consenti:consentSubmitted';
304
+ type ConsentiEventName = 'bannerInitialized' | 'consenti:bannerInitialized' | 'bannerVisibility' | 'consenti:bannerVisibility' | 'modalVisibility' | 'consenti:modalVisibility' | 'consentBeingSubmitted' | 'consenti:consentBeingSubmitted' | 'consentSubmitted' | 'consenti:consentSubmitted' | 'parentalConsentRequired' | 'consenti:parentalConsentRequired';
203
305
  interface ConsentiWidgetAPI {
204
306
  hasConsent(): boolean;
205
307
  getConsent(): ConsentValue | null;
308
+ getConsent(type: ConsentType): Record<string, string> | null;
309
+ getConsent(type?: ConsentType): ConsentValue | Record<string, string> | null;
206
310
  getConsentDate(): Date | false;
207
311
  getGTMConsent(): Record<string, string> | null;
208
312
  isCookieGranted(cookieId: string, requestValue?: boolean): boolean | ConsentStatus;
@@ -215,8 +319,8 @@ interface ConsentiWidgetAPI {
215
319
  off(event: ConsentiEventName, handler: (data: ConsentEvent) => void): void;
216
320
  version(): {
217
321
  package: string;
218
- profileVersion: number | null;
219
- consentVersion: number;
322
+ profileVersion: string | null;
323
+ consentVersion: string | null;
220
324
  };
221
325
  bannerVisibility(): 'main' | 'gpc' | false;
222
326
  modalVisibility(): 'preference' | false;
@@ -225,7 +329,7 @@ interface ConsentiWidgetAPI {
225
329
  hideBanner(): void;
226
330
  showModal(triggerEl?: HTMLElement): void;
227
331
  hideModal(): void;
228
- submitConsent(consent: Partial<ConsentValue>): Promise<void>;
332
+ submitConsent(consent: Partial<ConsentValue>): Promise<ConsentDbRecord | void>;
229
333
  deleteConsent(): Promise<void>;
230
334
  reConsent(): Promise<void>;
231
335
  getRootElement(): HTMLElement | null;
@@ -249,19 +353,47 @@ declare abstract class ConsentiPlugin {
249
353
  onModalShow?(): void | Promise<void>;
250
354
  onModalHide?(): void | Promise<void>;
251
355
  }
252
- interface ConsentEvent {
253
- profileId?: number;
254
- consent?: ConsentValue;
255
- hasExistingConsent?: boolean;
256
- show?: boolean;
257
- action?: boolean;
258
- consentAction?: ConsentAction;
259
- consentId?: string;
260
- pageUrl?: string;
261
- apiResponse?: unknown;
356
+ interface BannerInitializedDetail {
357
+ profileId: string;
358
+ complianceGroup?: ComplianceType;
359
+ hasExistingConsent: boolean;
360
+ gpcDetected: boolean;
361
+ willShow: boolean;
362
+ }
363
+ interface BannerVisibilityDetail {
364
+ visible: boolean;
365
+ variant: 'main' | 'gpc';
366
+ action: boolean;
367
+ }
368
+ interface ModalVisibilityDetail {
369
+ visible: boolean;
370
+ action: boolean;
371
+ }
372
+ interface ConsentBeingSubmitted {
373
+ consentId: string;
374
+ visitorId: string;
375
+ profileId: string;
376
+ consentJson: ConsentValue;
377
+ consentAction: ConsentAction;
378
+ gpcDetected: boolean;
379
+ pageUrl: string;
380
+ timestamp: number;
262
381
  fromBroadcast?: boolean;
263
- gpcDetected?: boolean;
264
382
  }
383
+ interface ConsentSubmittedDetail extends ConsentBeingSubmitted {
384
+ apiResponse: ConsentDbRecord;
385
+ }
386
+ /** Dispatched when the age gate is declined and `requireParentalConsent` is set — see
387
+ * `AgeGateWidgetConfig`. A deny-all consent (mandatory cookies only) has already been
388
+ * submitted with `parentalConsentToken` attached; this event is the hook for the site
389
+ * owner to run their own out-of-band parental-consent verification. */
390
+ interface ParentalConsentRequiredDetail {
391
+ parentalConsentToken: string;
392
+ profileId: string;
393
+ visitorId: string;
394
+ timestamp: number;
395
+ }
396
+ type ConsentEvent = BannerInitializedDetail | BannerVisibilityDetail | ModalVisibilityDetail | ConsentBeingSubmitted | ConsentSubmittedDetail | ParentalConsentRequiredDetail;
265
397
 
266
398
  /**
267
399
  * Main entry point for the Consenti widget.
@@ -281,16 +413,30 @@ interface ConsentEvent {
281
413
  */
282
414
 
283
415
  declare class ConsentiSetup implements ConsentiWidgetAPI {
284
- private config;
285
416
  private initialized;
286
417
  private initializing;
287
418
  private profile;
288
419
  private resolvedBase;
420
+ /** cookieId → Category lookup, rebuilt whenever `profile` changes. Avoids rescanning categories on every consent action. */
421
+ private categoryIndex;
422
+ /** Watchers created by the declarative `data-consenti-*` DOM scan — destroyed alongside the widget since they have no other owner. */
423
+ private scannedScripts;
289
424
  private consentStore;
425
+ /**
426
+ * Stable per-browser identifier. Created lazily the first time a consent decision actually
427
+ * happens (manual submit, GPC auto-response, or relaying a decision already made in another
428
+ * tab) — never minted before that, so an undecided visitor leaves nothing in storage. See
429
+ * `getOrCreateVisitorId()`.
430
+ */
431
+ private visitorId;
290
432
  private eventBus;
291
433
  private banner;
292
434
  private modal;
435
+ private ageGate;
293
436
  private renderer;
437
+ /** Set once the age gate (if enabled) resolves; threaded onto every `submitConsent` call thereafter. */
438
+ private _ageVerified;
439
+ private _parentalConsentToken;
294
440
  private broadcastChannel;
295
441
  private readyResolve;
296
442
  private readyPromise;
@@ -299,15 +445,23 @@ declare class ConsentiSetup implements ConsentiWidgetAPI {
299
445
  private lastSignature;
300
446
  /** Tracks which banner variant is currently visible, or null when hidden. */
301
447
  private _bannerVariant;
448
+ /** Tracks which banner variant is currently mounted in the DOM (persists when banner is hidden). */
449
+ private _bannerMounted;
450
+ /** Active focus trap keydown listener — removed when modal closes. */
451
+ private _focusTrapListener;
302
452
  /** Maps original handler functions to their DOM-wrapped listeners for on/off bookkeeping. */
303
453
  private _eventHandlers;
454
+ /** `core` defaulted to `{}` in the constructor below — every one of its own fields is
455
+ * optional, so this narrows back to non-optional for every other method in this class. */
456
+ private config;
304
457
  /**
305
458
  * Creates and initialises the consent widget.
306
459
  *
307
460
  * Construction is synchronous; the async `init()` is launched in the background.
308
461
  * Use `await widget.ready` or `widget.onReady(cb)` to run code after init completes.
309
462
  *
310
- * @param config - Widget configuration. See `ConsentiConfig` for all options.
463
+ * @param config - Widget configuration. See `ConsentiConfig` for all options. `core` may be
464
+ * omitted entirely — every one of its own fields is optional too.
311
465
  */
312
466
  constructor(config: ConsentiConfig);
313
467
  private profileId;
@@ -317,8 +471,53 @@ declare class ConsentiSetup implements ConsentiWidgetAPI {
317
471
  * to re-initialise the same instance.
318
472
  */
319
473
  init(): Promise<void>;
474
+ /**
475
+ * Everything after the "does consent already exist" check — GPC/CCPA/CPRA silent paths,
476
+ * then the normal opt-in banner. Extracted from `init()` so the age gate (when enabled and
477
+ * no consent exists yet) can defer this whole sequence until the visitor answers it via
478
+ * `resolveAgeGate()`, then resume here with `hasExisting` fixed at `false`.
479
+ */
480
+ private continueInit;
481
+ /** Mounts the age-gate prompt (`compliance.ageGate`) in place of the normal banner flow. */
482
+ private showAgeGate;
483
+ /**
484
+ * Handles the visitor's age-gate answer.
485
+ * - Confirmed → records `ageVerified: true` for all future `submitConsent` calls and
486
+ * resumes the normal init flow via `continueInit()`.
487
+ * - Declined, no parental consent required → immediately submits a deny-all consent
488
+ * (mandatory cookies still granted), mirroring the GPC-strict silent-write pattern.
489
+ * - Declined, parental consent required → same deny-all submission, plus a
490
+ * `parentalConsentToken` and a `consenti:parentalConsentRequired` event so the site
491
+ * owner can wire their own out-of-band verification flow (no email infra exists in
492
+ * this package to do that here).
493
+ */
494
+ private resolveAgeGate;
495
+ /**
496
+ * Marks the widget ready and runs the declarative DOM-scan for
497
+ * `data-consenti-consent-script`/`data-consenti-category-script` tags. Called from
498
+ * every exit path of `init()` so the scan always runs exactly once per init cycle,
499
+ * whether `init()` was triggered automatically (`autoInit !== false`) or explicitly
500
+ * by the caller (`autoInit: false`) — it never runs before the widget actually has
501
+ * a resolved profile to read consent from.
502
+ */
503
+ private finishInit;
504
+ /** Sets the `dir` attribute on the root element from `core.dir` (default `'auto'`, derived from `core.locale`). */
505
+ private applyDirection;
320
506
  /** Applies the `consenti-root--dark` class based on config or profile flag. */
321
507
  private applyDarkMode;
508
+ /** Applies `consenti--enhanced-a11y` class when `profile.enhanceAccessibility` is set. */
509
+ private applyAccessibility;
510
+ /** Builds and appends the metadata footer strip to a banner or modal container. */
511
+ private _appendMetaFooter;
512
+ /**
513
+ * Injects a `<style>` element into the root for `stackButtonsOnBreakpoint`.
514
+ * Called once per profile load; a new style replaces the previous one.
515
+ */
516
+ private applyStackButtonsBreakpoint;
517
+ /** Installs a keyboard focus trap on `container` while the modal is open. */
518
+ private _installFocusTrap;
519
+ /** Removes the active focus trap keyboard listener. */
520
+ private _removeFocusTrap;
322
521
  /**
323
522
  * Injects CSS custom property overrides supplied via `core.theme` into the
324
523
  * Consenti root element so they cascade down to all widget elements.
@@ -348,22 +547,23 @@ declare class ConsentiSetup implements ConsentiWidgetAPI {
348
547
  */
349
548
  hasConsent(): boolean;
350
549
  /**
351
- * Returns the stored consent map, or `null` if no consent has been saved.
352
- * Always returns `null` during SSR.
550
+ * Returns the stored consent map, optionally transformed for a specific vendor.
551
+ * Pass `type` to get a vendor-ready object instead of the raw Consenti format.
552
+ * Always returns `null` during SSR or when no consent has been saved.
553
+ *
554
+ * @param type - Vendor format: 'google-gtm', 'category', 'adobe', 'meta',
555
+ * 'microsoft-clarity', 'twilio-segment', or omit for raw format.
353
556
  */
354
557
  getConsent(): ConsentValue | null;
558
+ getConsent(type: ConsentType): Record<string, string> | null;
355
559
  /**
356
560
  * Returns the `Date` when consent was last saved, or `false` if no consent exists.
357
561
  * Always returns `false` during SSR.
358
562
  */
359
563
  getConsentDate(): Date | false;
360
564
  /**
361
- * Returns the consent map in Google Consent Mode v2 format, ready to pass to
362
- * `gtag('consent', 'update', ...)`. Returns `null` if no consent has been saved.
363
- *
364
- * Maps Consenti cookie IDs to the seven Google Consent Mode v2 parameters.
365
- * Cookies whose IDs exactly match a GCM v2 key are forwarded directly.
366
- * Non-GCM cookies are ignored by this method (they appear in `getConsent()` only).
565
+ * @deprecated Use `getConsent('google-gtm')` instead.
566
+ * Returns the consent map in Google Consent Mode v2 format.
367
567
  */
368
568
  getGTMConsent(): Record<string, string> | null;
369
569
  /**
@@ -375,7 +575,7 @@ declare class ConsentiSetup implements ConsentiWidgetAPI {
375
575
  * @param consent - Partial or full map of cookie ID → status.
376
576
  * @param gpcDerived - Internal flag set to `true` when called from the GPC strict path.
377
577
  */
378
- submitConsent(consent: Partial<ConsentValue>, gpcDerived?: boolean): Promise<void>;
578
+ submitConsent(consent: Partial<ConsentValue>, gpcDerived?: boolean, source?: number): Promise<ConsentDbRecord | void>;
379
579
  /**
380
580
  * Returns the currently visible banner variant, or `false` if no banner is shown.
381
581
  * Useful for programmatically checking state before calling `showBanner` / `hideBanner`.
@@ -544,8 +744,8 @@ declare class ConsentiSetup implements ConsentiWidgetAPI {
544
744
  */
545
745
  version(): {
546
746
  package: string;
547
- profileVersion: number | null;
548
- consentVersion: number;
747
+ profileVersion: string;
748
+ consentVersion: string | null;
549
749
  };
550
750
  /**
551
751
  * Toggles or sets dark mode at runtime without re-initialising the widget.
@@ -623,7 +823,7 @@ declare function useConsent(): {
623
823
  /** Denies all non-mandatory cookies. Pass `true` to deny mandatory ones too (use with care). */
624
824
  denyAll: (includingMandatory?: boolean) => Promise<void>;
625
825
  /** Saves the given partial consent map. */
626
- submitConsent: (c: Partial<ConsentValue>) => Promise<void>;
826
+ submitConsent: (c: Partial<ConsentValue>) => Promise<void | ConsentDbRecord>;
627
827
  /** Deletes the current consent record and re-shows the banner. */
628
828
  reConsent: () => Promise<void>;
629
829
  /** Returns `true` if the given cookie ID is `'granted'`. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@consenti/ui",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Zero-dependency GDPR cookie consent widget",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -64,7 +64,8 @@
64
64
  "dist"
65
65
  ],
66
66
  "scripts": {
67
- "build": "tsup && npm run build:css",
67
+ "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
68
+ "build": "npm run clean && tsup && npm run build:css",
68
69
  "dev:build": "tsup --watch",
69
70
  "dev:vite": "vite",
70
71
  "dev": "npm run dev:vite & npm run dev:build",
@@ -76,14 +77,14 @@
76
77
  "devDependencies": {
77
78
  "@consenti/types": "*",
78
79
  "@consenti/utils": "*",
79
- "@types/node": "^22.0.0",
80
+ "@types/node": "^26.1.1",
80
81
  "@types/react": "^19.0.0",
81
- "tsx": "^4.23.0",
82
- "vue": "^3.5.0",
83
82
  "sass": "^1.89.2",
84
83
  "tsup": "^8.5.0",
84
+ "tsx": "^4.23.0",
85
85
  "typescript": "^6.0.3",
86
- "vite": "^8.1.3"
86
+ "vite": "^8.1.5",
87
+ "vue": "^3.5.40"
87
88
  },
88
89
  "peerDependencies": {
89
90
  "react": ">=18.0.0",
@@ -102,4 +103,4 @@
102
103
  "node": ">=20.0.0",
103
104
  "npm": ">=10.0.0"
104
105
  }
105
- }
106
+ }
@@ -1,2 +0,0 @@
1
- async function i(e,s={},n){let r={"Content-Type":"application/json",...s.headers};n&&(r.Authorization=`Bearer ${n}`);let t=await fetch(e,{...s,headers:r});if(!t.ok)throw new Error(`[Consenti] HTTP ${t.status} ${t.statusText} \u2014 ${e}`);return t.json()}export{i as a};
2
- //# sourceMappingURL=chunk-K2UN3O67.mjs.map
@@ -1,2 +0,0 @@
1
- import{a}from"./chunk-K2UN3O67.mjs";export{a as httpRequest};
2
- //# sourceMappingURL=http-UU2NIW6M.mjs.map