@consenti/ui 0.1.0 → 0.2.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/http.ts"],"sourcesContent":["/**\n * Minimal `fetch` wrapper used for all Consenti API calls.\n *\n * Adds `Content-Type: application/json` and, when `authToken` is provided,\n * an `Authorization: Bearer <token>` header. Throws a descriptive `Error` on\n * non-2xx responses so callers can catch and fall back gracefully.\n *\n * This module is tree-shaken out of bundles that do not use API mode.\n */\n\n/**\n * Sends an HTTP request and returns the parsed JSON response body.\n *\n * @param url - The request URL.\n * @param options - Standard `RequestInit` options (method, body, headers, etc.).\n * @param authToken - Optional Bearer token. Added as `Authorization: Bearer <token>` when present.\n * @returns Parsed JSON body typed as `T`.\n * @throws `Error` with HTTP status and URL on non-2xx responses.\n */\nexport async function httpRequest<T>(\n url: string,\n options: RequestInit = {},\n authToken?: string,\n): Promise<T> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...(options.headers as Record<string, string> | undefined),\n }\n if (authToken) {\n headers['Authorization'] = `Bearer ${authToken}`\n }\n const res = await fetch(url, { ...options, headers })\n if (!res.ok) {\n throw new Error(`[Consenti] HTTP ${res.status} ${res.statusText} — ${url}`)\n }\n return res.json() as Promise<T>\n}\n"],"mappings":";AAmBA,eAAsB,YACpB,KACA,UAAuB,CAAC,GACxB,WACY;AACZ,QAAM,UAAkC;AAAA,IACtC,gBAAgB;AAAA,IAChB,GAAI,QAAQ;AAAA,EACd;AACA,MAAI,WAAW;AACb,YAAQ,eAAe,IAAI,UAAU,SAAS;AAAA,EAChD;AACA,QAAM,MAAM,MAAM,MAAM,KAAK,EAAE,GAAG,SAAS,QAAQ,CAAC;AACpD,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,MAAM,mBAAmB,IAAI,MAAM,IAAI,IAAI,UAAU,WAAM,GAAG,EAAE;AAAA,EAC5E;AACA,SAAO,IAAI,KAAK;AAClB;","names":[]}
1
+ {"version":3,"sources":["../src/utils/http.ts"],"sourcesContent":["/**\n * Minimal `fetch` wrapper used for all Consenti API calls.\n *\n * Adds `Content-Type: application/json` and, when `authToken` is provided,\n * an `Authorization: Bearer <token>` header. Throws a descriptive `Error` on\n * non-2xx responses so callers can catch and fall back gracefully.\n *\n * This module is tree-shaken out of bundles that do not use API mode.\n */\n\n/**\n * Sends an HTTP request and returns the parsed JSON response body.\n *\n * @param url - The request URL.\n * @param options - Standard `RequestInit` options (method, body, headers, etc.).\n * @param authToken - Optional Bearer token. Added as `Authorization: Bearer <token>` when present.\n * @returns Parsed JSON body typed as `T`.\n * @throws `Error` with HTTP status and URL on non-2xx responses.\n */\nexport async function httpRequest<T>(\n url: string,\n options: RequestInit = {},\n authToken?: string,\n): Promise<T> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...(options.headers as Record<string, string> | undefined),\n }\n if (authToken) {\n headers['Authorization'] = `Bearer ${authToken}`\n }\n const res = await fetch(url, { ...options, headers })\n if (!res.ok) {\n throw new Error(`[Consenti] HTTP ${res.status} ${res.statusText} — ${url}`)\n }\n return res.json() as Promise<T>\n}\n"],"mappings":"AAmBA,eAAsBA,EACpBC,EACAC,EAAuB,CAAC,EACxBC,EACY,CACZ,IAAMC,EAAkC,CACtC,eAAgB,mBAChB,GAAIF,EAAQ,OACd,EACIC,IACFC,EAAQ,cAAmB,UAAUD,CAAS,IAEhD,IAAME,EAAM,MAAM,MAAMJ,EAAK,CAAE,GAAGC,EAAS,QAAAE,CAAQ,CAAC,EACpD,GAAI,CAACC,EAAI,GACP,MAAM,IAAI,MAAM,mBAAmBA,EAAI,MAAM,IAAIA,EAAI,UAAU,WAAMJ,CAAG,EAAE,EAE5E,OAAOI,EAAI,KAAK,CAClB","names":["httpRequest","url","options","authToken","headers","res"]}
@@ -0,0 +1,2 @@
1
+ async function i(t){let e=new TextEncoder().encode(t);return crypto.subtle.importKey("raw",e,{name:"HMAC",hash:"SHA-256"},!1,["sign","verify"])}async function s(t,e){let n=await i(e),r=new TextEncoder().encode(t),o=await crypto.subtle.sign("HMAC",n,r);return Array.from(new Uint8Array(o)).map(a=>a.toString(16).padStart(2,"0")).join("")}async function c(t,e,n){return await s(t,n)===e}export{s as signValue,c as verifyValue};
2
+ //# sourceMappingURL=hmac-UKMRMKN6.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/hmac.ts"],"sourcesContent":["/**\n * HMAC-SHA256 signing utilities using the native Web Crypto API.\n *\n * Used to sign consent cookies so that tampering (e.g. a user manually editing\n * their cookie to fake a broader consent) is detectable on the next page load.\n * The signing key is sourced from the API profile response or from\n * `core.cookieSigningKey` in the widget config.\n *\n * All operations are async because `crypto.subtle` is Promise-based.\n * This module is tree-shaken out of bundles that do not use `core.signCookies`.\n */\n\n/**\n * Imports a raw UTF-8 string key as a non-extractable `CryptoKey` for HMAC-SHA256.\n * The key is not extractable — it can only be used for sign/verify operations.\n */\nasync function importHmacKey(rawKey: string): Promise<CryptoKey> {\n const keyData = new TextEncoder().encode(rawKey)\n return crypto.subtle.importKey(\n 'raw',\n keyData,\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign', 'verify'],\n )\n}\n\n/**\n * Signs a string value with HMAC-SHA256 and returns the lowercase hex-encoded signature.\n *\n * @param value - The plaintext string to sign (the raw cookie value without `::sig:` segment).\n * @param key - The signing key as a UTF-8 string.\n * @returns Lowercase hex string, e.g. `\"a3f9c2e1b8d0...\"`\n */\nexport async function signValue(value: string, key: string): Promise<string> {\n const cryptoKey = await importHmacKey(key)\n const data = new TextEncoder().encode(value)\n const signature = await crypto.subtle.sign('HMAC', cryptoKey, data)\n return Array.from(new Uint8Array(signature))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\n/**\n * Verifies a HMAC-SHA256 signature by re-computing the expected signature and comparing.\n *\n * Note: comparison is not constant-time, but this runs entirely client-side against\n * data the user already controls, so timing attacks offer no practical advantage.\n *\n * @param value - The original plaintext string that was signed.\n * @param signature - The hex-encoded signature to verify.\n * @param key - The signing key as a UTF-8 string.\n * @returns `true` if the signature matches, `false` otherwise.\n */\nexport async function verifyValue(value: string, signature: string, key: string): Promise<boolean> {\n const expected = await signValue(value, key)\n return expected === signature\n}\n"],"mappings":";AAgBA,eAAe,cAAc,QAAoC;AAC/D,QAAM,UAAU,IAAI,YAAY,EAAE,OAAO,MAAM;AAC/C,SAAO,OAAO,OAAO;AAAA,IACnB;AAAA,IACA;AAAA,IACA,EAAE,MAAM,QAAQ,MAAM,UAAU;AAAA,IAChC;AAAA,IACA,CAAC,QAAQ,QAAQ;AAAA,EACnB;AACF;AASA,eAAsB,UAAU,OAAe,KAA8B;AAC3E,QAAM,YAAY,MAAM,cAAc,GAAG;AACzC,QAAM,OAAO,IAAI,YAAY,EAAE,OAAO,KAAK;AAC3C,QAAM,YAAY,MAAM,OAAO,OAAO,KAAK,QAAQ,WAAW,IAAI;AAClE,SAAO,MAAM,KAAK,IAAI,WAAW,SAAS,CAAC,EACxC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAC1C,KAAK,EAAE;AACZ;AAaA,eAAsB,YAAY,OAAe,WAAmB,KAA+B;AACjG,QAAM,WAAW,MAAM,UAAU,OAAO,GAAG;AAC3C,SAAO,aAAa;AACtB;","names":[]}
1
+ {"version":3,"sources":["../src/utils/hmac.ts"],"sourcesContent":["/**\n * HMAC-SHA256 signing utilities using the native Web Crypto API.\n *\n * Used to sign consent cookies so that tampering (e.g. a user manually editing\n * their cookie to fake a broader consent) is detectable on the next page load.\n * The signing key is sourced from the API profile response or from\n * `core.cookieSigningKey` in the widget config.\n *\n * All operations are async because `crypto.subtle` is Promise-based.\n * This module is tree-shaken out of bundles that do not use `core.signCookies`.\n */\n\n/**\n * Imports a raw UTF-8 string key as a non-extractable `CryptoKey` for HMAC-SHA256.\n * The key is not extractable — it can only be used for sign/verify operations.\n */\nasync function importHmacKey(rawKey: string): Promise<CryptoKey> {\n const keyData = new TextEncoder().encode(rawKey)\n return crypto.subtle.importKey(\n 'raw',\n keyData,\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign', 'verify'],\n )\n}\n\n/**\n * Signs a string value with HMAC-SHA256 and returns the lowercase hex-encoded signature.\n *\n * @param value - The plaintext string to sign (the raw cookie value without `::sig:` segment).\n * @param key - The signing key as a UTF-8 string.\n * @returns Lowercase hex string, e.g. `\"a3f9c2e1b8d0...\"`\n */\nexport async function signValue(value: string, key: string): Promise<string> {\n const cryptoKey = await importHmacKey(key)\n const data = new TextEncoder().encode(value)\n const signature = await crypto.subtle.sign('HMAC', cryptoKey, data)\n return Array.from(new Uint8Array(signature))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\n/**\n * Verifies a HMAC-SHA256 signature by re-computing the expected signature and comparing.\n *\n * Note: comparison is not constant-time, but this runs entirely client-side against\n * data the user already controls, so timing attacks offer no practical advantage.\n *\n * @param value - The original plaintext string that was signed.\n * @param signature - The hex-encoded signature to verify.\n * @param key - The signing key as a UTF-8 string.\n * @returns `true` if the signature matches, `false` otherwise.\n */\nexport async function verifyValue(value: string, signature: string, key: string): Promise<boolean> {\n const expected = await signValue(value, key)\n return expected === signature\n}\n"],"mappings":"AAgBA,eAAeA,EAAcC,EAAoC,CAC/D,IAAMC,EAAU,IAAI,YAAY,EAAE,OAAOD,CAAM,EAC/C,OAAO,OAAO,OAAO,UACnB,MACAC,EACA,CAAE,KAAM,OAAQ,KAAM,SAAU,EAChC,GACA,CAAC,OAAQ,QAAQ,CACnB,CACF,CASA,eAAsBC,EAAUC,EAAeC,EAA8B,CAC3E,IAAMC,EAAY,MAAMN,EAAcK,CAAG,EACnCE,EAAO,IAAI,YAAY,EAAE,OAAOH,CAAK,EACrCI,EAAY,MAAM,OAAO,OAAO,KAAK,OAAQF,EAAWC,CAAI,EAClE,OAAO,MAAM,KAAK,IAAI,WAAWC,CAAS,CAAC,EACxC,IAAKC,GAAMA,EAAE,SAAS,EAAE,EAAE,SAAS,EAAG,GAAG,CAAC,EAC1C,KAAK,EAAE,CACZ,CAaA,eAAsBC,EAAYN,EAAeI,EAAmBH,EAA+B,CAEjG,OADiB,MAAMF,EAAUC,EAAOC,CAAG,IACvBG,CACtB","names":["importHmacKey","rawKey","keyData","signValue","value","key","cryptoKey","data","signature","b","verifyValue"]}
@@ -0,0 +1,2 @@
1
+ import{a}from"./chunk-K2UN3O67.mjs";export{a as httpRequest};
2
+ //# sourceMappingURL=http-UU2NIW6M.mjs.map
package/dist/index.css CHANGED
@@ -1 +1 @@
1
- :root{--consenti-color-bg: #ffffff;--consenti-color-text: #1a2e4a;--consenti-color-primary: #1565c0;--consenti-color-primary-text: #ffffff;--consenti-color-secondary: #f0f4f8;--consenti-color-secondary-text: #1a2e4a;--consenti-color-border: #dbe4ee;--consenti-color-overlay: rgba(26, 52, 96, 0.55);--consenti-font-family: system-ui, -apple-system, sans-serif;--consenti-font-size-base: 14px;--consenti-font-size-heading: 16px;--consenti-font-weight-heading: 600;--consenti-line-height: 1.5;--consenti-spacing-sm: 8px;--consenti-spacing-md: 16px;--consenti-spacing-lg: 24px;--consenti-border-radius: 8px;--consenti-border-radius-btn: 4px;--consenti-shadow: 0 4px 24px rgba(21, 101, 192, 0.14);--consenti-color-accent: #d32f2f;--consenti-color-accent-text: #ffffff;--consenti-toggle-bg-on: #43a047;--consenti-toggle-bg-off: #c8d6e8;--consenti-toggle-knob: #ffffff;--consenti-toggle-width: 44px;--consenti-toggle-height: 24px;--consenti-z-banner: 9999;--consenti-z-overlay: 9998;--consenti-z-modal: 10000}.consenti-root--dark{--consenti-color-bg: #1e2535;--consenti-color-text: #e2e8f0;--consenti-color-primary: #4a9eff;--consenti-color-primary-text: #ffffff;--consenti-color-secondary: #2a3447;--consenti-color-secondary-text: #cbd5e1;--consenti-color-border: #374151;--consenti-color-overlay: rgba(0, 0, 0, 0.65);--consenti-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);--consenti-toggle-bg-off: #4b5563}.consenti-overlay{position:fixed;inset:0;background:var(--consenti-color-overlay);z-index:var(--consenti-z-overlay)}.consenti-btn{display:inline-flex;align-items:center;justify-content:center;padding:var(--consenti-spacing-sm) var(--consenti-spacing-md);border:none;border-radius:var(--consenti-border-radius-btn);font-family:var(--consenti-font-family);font-size:var(--consenti-font-size-base);font-weight:500;line-height:1;cursor:pointer;text-decoration:none;transition:opacity .15s ease}.consenti-btn:focus-visible{outline:2px solid var(--consenti-color-primary);outline-offset:2px}.consenti-btn:hover{opacity:.9}.consenti-btn--primary{background:var(--consenti-color-primary);color:var(--consenti-color-primary-text)}.consenti-btn--secondary{background:var(--consenti-color-secondary);color:var(--consenti-color-secondary-text)}.consenti-btn--text{background:rgba(0,0,0,0);color:var(--consenti-color-primary);padding-left:0;padding-right:0;text-decoration:underline}.consenti-btn--accent{background:var(--consenti-color-accent);color:var(--consenti-color-accent-text)}.consenti-btn--close{background:rgba(0,0,0,0);color:var(--consenti-color-text);font-size:20px;line-height:1;padding:4px 8px;border-radius:50%}.consenti-btn--close:hover{background:var(--consenti-color-secondary);opacity:1}.consenti-banner{position:fixed;left:0;right:0;z-index:var(--consenti-z-banner);background:var(--consenti-color-bg);color:var(--consenti-color-text);font-family:var(--consenti-font-family);font-size:var(--consenti-font-size-base);line-height:var(--consenti-line-height);box-shadow:var(--consenti-shadow);padding:var(--consenti-spacing-lg);display:flex;flex-wrap:wrap;align-items:center;gap:var(--consenti-spacing-md)}.consenti-banner--top{top:0;border-bottom:1px solid var(--consenti-color-border)}@media(prefers-reduced-motion: no-preference){.consenti-banner--top{animation:consenti-slide-down .2s ease-out}}.consenti-banner--bottom{bottom:0;border-top:1px solid var(--consenti-color-border)}@media(prefers-reduced-motion: no-preference){.consenti-banner--bottom{animation:consenti-slide-up .2s ease-out}}.consenti-banner--middle{top:50%;transform:translateY(-50%);left:50%;right:auto;transform:translate(-50%, -50%);width:min(600px,90vw);border-radius:var(--consenti-border-radius)}.consenti-banner--left-bottom{bottom:var(--consenti-spacing-lg);left:var(--consenti-spacing-lg);right:auto;width:min(400px,90vw);border-radius:var(--consenti-border-radius)}.consenti-banner--right-bottom{bottom:var(--consenti-spacing-lg);right:var(--consenti-spacing-lg);left:auto;width:min(400px,90vw);border-radius:var(--consenti-border-radius)}.consenti-banner__text-container{flex:1;min-width:200px}.consenti-banner__heading{font-size:var(--consenti-font-size-heading);font-weight:var(--consenti-font-weight-heading);margin:0 0 var(--consenti-spacing-sm);width:100%}.consenti-banner__text{margin:0}.consenti-banner__links{display:flex;flex-wrap:wrap;gap:var(--consenti-spacing-sm);margin-top:var(--consenti-spacing-sm)}.consenti-banner__link{font-size:.85em;text-decoration:underline;text-underline-offset:2px}.consenti-banner__controls{position:absolute;top:var(--consenti-spacing-sm);right:var(--consenti-spacing-sm);display:flex;align-items:center;gap:4px}.consenti-banner__buttons{display:flex;flex-wrap:wrap;gap:var(--consenti-spacing-sm);align-items:center}.consenti-banner--top .consenti-banner__buttons,.consenti-banner--bottom .consenti-banner__buttons{margin-left:2vw}@keyframes consenti-slide-up{from{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes consenti-slide-down{from{transform:translateY(-100%)}to{transform:translateY(0)}}.consenti-modal{position:fixed;inset:0;z-index:var(--consenti-z-modal);display:flex;font-family:var(--consenti-font-family);font-size:var(--consenti-font-size-base);color:var(--consenti-color-text);line-height:var(--consenti-line-height)}.consenti-modal--passthrough{pointer-events:none}.consenti-modal--passthrough .consenti-modal__container{pointer-events:auto}.consenti-modal--center{align-items:center;justify-content:center}.consenti-modal--left{align-items:stretch;justify-content:flex-start}.consenti-modal--left .consenti-modal__container{border-radius:0 var(--consenti-border-radius) var(--consenti-border-radius) 0;height:100%;max-height:100vh}.consenti-modal--right{align-items:stretch;justify-content:flex-end}.consenti-modal--right .consenti-modal__container{border-radius:0;height:100%;max-height:100vh}.consenti-modal__overlay{position:absolute;inset:0;background:var(--consenti-color-overlay)}.consenti-modal__container{position:relative;z-index:1;background:var(--consenti-color-bg);border-radius:var(--consenti-border-radius);box-shadow:var(--consenti-shadow);width:min(560px,90vw);max-height:85vh;display:flex;flex-direction:column;overflow:hidden}@media(prefers-reduced-motion: no-preference){.consenti-modal__container{animation:consenti-fade-in .15s ease-out}}.consenti-modal__header{display:flex;align-items:center;justify-content:space-between;padding:var(--consenti-spacing-md) var(--consenti-spacing-lg);border-bottom:1px solid var(--consenti-color-border);flex-shrink:0}.consenti-modal__heading{font-size:var(--consenti-font-size-heading);font-weight:var(--consenti-font-weight-heading);margin:0;flex:1}.consenti-modal__header-controls{display:flex;align-items:center;gap:4px;flex-shrink:0}.consenti-modal__close{flex-shrink:0}.consenti-modal__links{display:flex;flex-wrap:wrap;gap:var(--consenti-spacing-sm);padding:var(--consenti-spacing-sm) var(--consenti-spacing-lg)}.consenti-modal__link{font-size:.85em;text-decoration:underline;text-underline-offset:2px}.consenti-modal__subheading{padding:var(--consenti-spacing-sm) var(--consenti-spacing-lg) 0;margin:0;color:#6b7280;font-size:13px}.consenti-modal__text{padding:var(--consenti-spacing-sm) var(--consenti-spacing-lg)}.consenti-modal__body{overflow-y:auto;flex:1;padding:0 var(--consenti-spacing-lg)}.consenti-modal__categories{display:flex;flex-direction:column;gap:var(--consenti-spacing-md);padding:var(--consenti-spacing-md) 0}.consenti-modal__buttons{display:flex;flex-wrap:wrap;gap:var(--consenti-spacing-sm);padding:var(--consenti-spacing-md) var(--consenti-spacing-lg);border-top:1px solid var(--consenti-color-border);flex-shrink:0}.consenti-modal__receipt-option{margin:0 var(--consenti-spacing-lg);padding-top:var(--consenti-spacing-md);border-top:1px solid var(--consenti-color-border);flex-shrink:0}.consenti-modal__receipt-label{display:flex;align-items:center;gap:var(--consenti-spacing-sm);font-size:var(--consenti-font-size-base);cursor:pointer}.consenti-modal__receipt-checkbox{width:16px;height:16px;flex-shrink:0;cursor:pointer}.consenti-modal__receipt-desc{margin:4px 0 0 24px;font-size:12px;color:#6b7280}@keyframes consenti-fade-in{from{opacity:0;transform:scale(0.97)}to{opacity:1;transform:scale(1)}}.consenti-category{padding:var(--consenti-spacing-md) 0;border-bottom:1px solid var(--consenti-color-border)}.consenti-category:last-child{border-bottom:none}.consenti-category--mandatory .consenti-category__toggle{opacity:.6;cursor:default}.consenti-category__header{display:flex;align-items:center;justify-content:space-between;gap:var(--consenti-spacing-md);margin-bottom:var(--consenti-spacing-sm)}.consenti-category__heading{font-size:var(--consenti-font-size-heading);font-weight:var(--consenti-font-weight-heading);margin:0}.consenti-category__text{color:#4b5563;font-size:13px;margin:0}.consenti-category__li-desc{margin:var(--consenti-spacing-sm) 0 0;font-size:12px;color:#6b7280;font-style:italic}.consenti-category__toggle{flex-shrink:0;position:relative;width:var(--consenti-toggle-width);height:var(--consenti-toggle-height);border-radius:calc(var(--consenti-toggle-height)/2);background:var(--consenti-toggle-bg-off);border:none;cursor:pointer;transition:background .2s ease;padding:0}.consenti-category__toggle:focus-visible{outline:2px solid var(--consenti-color-primary);outline-offset:2px}.consenti-category__toggle[aria-checked=true]{background:var(--consenti-toggle-bg-on)}.consenti-category__toggle--mandatory{cursor:default}.consenti-category__toggle-knob{position:absolute;top:2px;left:2px;width:calc(var(--consenti-toggle-height) - 4px);height:calc(var(--consenti-toggle-height) - 4px);border-radius:50%;background:var(--consenti-toggle-knob);transition:transform .2s ease;display:block}[aria-checked=true] .consenti-category__toggle-knob{transform:translateX(calc(var(--consenti-toggle-width) - var(--consenti-toggle-height)))}.consenti-locale-switcher{position:relative;display:inline-flex}.consenti-locale-switcher__btn{font-size:1rem;line-height:1;cursor:pointer}.consenti-locale-switcher__list{position:absolute;top:calc(100% + 4px);right:0;z-index:calc(var(--consenti-z-modal) + 1);background:var(--consenti-color-bg);border:1px solid var(--consenti-color-border);border-radius:var(--consenti-border-radius);box-shadow:var(--consenti-shadow);list-style:none;margin:0;padding:4px 0;min-width:80px}.consenti-locale-switcher__list li{display:block}.consenti-locale-switcher__option{display:block;width:100%;padding:6px 12px;text-align:left;font-size:var(--consenti-font-size-base);color:var(--consenti-color-text);background:none;border:none;cursor:pointer}.consenti-locale-switcher__option:hover{background:var(--consenti-color-secondary)}.consenti-locale-switcher__option--active{font-weight:600;color:var(--consenti-color-primary)}.consenti-locale-switcher--modal .consenti-locale-switcher__list{right:0}
1
+ #consenti-root{position:relative}.consenti-d-none{display:none}:root{--consenti-color-bg: #ffffff;--consenti-color-text: #1a2e4a;--consenti-color-primary: #1565c0;--consenti-color-primary-text: #ffffff;--consenti-color-secondary: #f0f4f8;--consenti-color-secondary-text: #1a2e4a;--consenti-color-border: #dbe4ee;--consenti-color-secondary-border: #1a2e4a;--consenti-color-overlay: rgba(26, 52, 96, 0.55);--consenti-color-accent: #d32f2f;--consenti-color-accent-text: #ffffff;--consenti-font-family: system-ui, -apple-system, sans-serif;--consenti-font-size-base: 14px;--consenti-font-size-heading: 16px;--consenti-font-weight-heading: 600;--consenti-line-height: 1.5;--consenti-spacing-sm: 8px;--consenti-spacing-md: 16px;--consenti-spacing-lg: 24px;--consenti-border-radius: 8px;--consenti-border-radius-btn: 0;--consenti-shadow: 0 4px 24px rgba(21, 101, 192, 0.14);--consenti-toggle-bg-on: #43a047;--consenti-toggle-bg-off: #9ca3af;--consenti-toggle-knob: #ffffff;--consenti-toggle-width: 52px;--consenti-toggle-height: 28px;--consenti-z-banner: 9999;--consenti-z-overlay: 9998;--consenti-z-modal: 10000}.consenti-root--dark{--consenti-color-bg: #1e2535;--consenti-color-text: #e2e8f0;--consenti-color-primary: #4a9eff;--consenti-color-primary-text: #ffffff;--consenti-color-secondary: #2a3447;--consenti-color-secondary-text: #cbd5e1;--consenti-color-border: #374151;--consenti-color-overlay: rgba(0, 0, 0, 0.65);--consenti-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);--consenti-toggle-bg-off: #6b7280}.consenti-overlay{position:fixed;inset:0;background:var(--consenti-color-overlay);z-index:var(--consenti-z-overlay)}.consenti-btn{display:inline-flex;align-items:center;justify-content:center;padding:var(--consenti-spacing-sm) var(--consenti-spacing-md);border:none;border-radius:var(--consenti-border-radius-btn);font-family:var(--consenti-font-family);font-size:var(--consenti-font-size-base);font-weight:500;line-height:1;cursor:pointer;text-decoration:none;transition:opacity .15s ease}.consenti-btn:focus-visible{outline:2px solid var(--consenti-color-primary);outline-offset:2px}.consenti-btn:hover{opacity:.9}.consenti-btn--primary{background:var(--consenti-color-primary);color:var(--consenti-color-primary-text)}.consenti-btn--secondary{background:var(--consenti-color-secondary);color:var(--consenti-color-primary);border:1px solid var(--consenti-color-primary)}.consenti-btn--secondary:hover{background:var(--consenti-color-primary);color:var(--consenti-color-secondary);border:1px solid var(--consenti-color-secondary)}.consenti-btn--text{background:rgba(0,0,0,0);color:var(--consenti-color-primary);padding-left:0;padding-right:0;text-decoration:underline;text-underline-offset:4px}.consenti-btn--accent{background:var(--consenti-color-accent);color:var(--consenti-color-accent-text)}.consenti-btn--close{background:rgba(0,0,0,0);color:var(--consenti-color-text);font-size:20px;line-height:1;padding:4px 8px;border-radius:50%}.consenti-btn--close:hover{background:var(--consenti-color-secondary);opacity:1}.consenti-banner{position:fixed;left:0;right:0;z-index:var(--consenti-z-banner);background:var(--consenti-color-bg);color:var(--consenti-color-text);font-family:var(--consenti-font-family);font-size:var(--consenti-font-size-base);line-height:var(--consenti-line-height);box-shadow:var(--consenti-shadow);padding:var(--consenti-spacing-lg);display:flex;flex-wrap:wrap;align-items:center;gap:var(--consenti-spacing-md)}.consenti-banner--top{top:0;border-bottom:1px solid var(--consenti-color-border)}@media(prefers-reduced-motion: no-preference){.consenti-banner--top{animation:consenti-slide-down .2s ease-out}}.consenti-banner--bottom{bottom:0;border-top:1px solid var(--consenti-color-border)}@media(prefers-reduced-motion: no-preference){.consenti-banner--bottom{animation:consenti-slide-up .2s ease-out}}.consenti-banner--middle{top:50%;transform:translateY(-50%);left:50%;right:auto;transform:translate(-50%, -50%);width:min(600px,90vw);border-radius:var(--consenti-border-radius)}.consenti-banner--left-bottom{bottom:var(--consenti-spacing-lg);left:var(--consenti-spacing-lg);right:auto;width:min(400px,90vw);border-radius:var(--consenti-border-radius)}.consenti-banner--right-bottom{bottom:var(--consenti-spacing-lg);right:var(--consenti-spacing-lg);left:auto;width:min(400px,90vw);border-radius:var(--consenti-border-radius)}.consenti-banner__text-container{flex:1;min-width:500px}.consenti-banner__heading{font-size:var(--consenti-font-size-heading);font-weight:var(--consenti-font-weight-heading);margin:0 0 var(--consenti-spacing-sm);width:100%}.consenti-banner__text{margin:0}.consenti-banner__links{display:flex;flex-wrap:wrap;gap:var(--consenti-spacing-sm);margin-top:var(--consenti-spacing-sm)}.consenti-banner__link{font-size:.85em}.consenti-banner__controls{position:absolute;top:var(--consenti-spacing-sm);right:var(--consenti-spacing-sm);display:flex;align-items:center;gap:4px}.consenti-banner__buttons{display:flex;flex-wrap:wrap;gap:var(--consenti-spacing-sm);align-items:center;margin-block:var(--consenti-spacing-md)}.consenti-banner__powered-by{position:absolute;right:10px;bottom:2px;opacity:.2;color:var(--consenti-color-primary)}@media(max-width: 576px){.consenti-banner{padding:var(--consenti-spacing-md);gap:var(--consenti-spacing-sm)}.consenti-banner--left-bottom,.consenti-banner--right-bottom{left:0;right:0;bottom:0;width:100%;border-radius:0;border-top:1px solid var(--consenti-color-border)}.consenti-banner--middle{width:95vw}.consenti-banner__buttons{width:100%;flex-direction:row;align-items:stretch;margin-bottom:var(--consenti-spacing-md)}.consenti-banner__text-container{min-width:0;width:100%}}@keyframes consenti-slide-up{from{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes consenti-slide-down{from{transform:translateY(-100%)}to{transform:translateY(0)}}.consenti-modal{position:fixed;inset:0;z-index:var(--consenti-z-modal);display:flex;font-family:var(--consenti-font-family);font-size:var(--consenti-font-size-base);color:var(--consenti-color-text);line-height:var(--consenti-line-height)}.consenti-modal--passthrough{pointer-events:none}.consenti-modal--passthrough .consenti-modal__container{pointer-events:auto}.consenti-modal--center{align-items:center;justify-content:center}.consenti-modal--left{align-items:stretch;justify-content:flex-start}.consenti-modal--left .consenti-modal__container{border-radius:0 var(--consenti-border-radius) var(--consenti-border-radius) 0;height:100%;max-height:100vh}.consenti-modal--right{align-items:stretch;justify-content:flex-end}.consenti-modal--right .consenti-modal__container{border-radius:0;height:100%;max-height:100vh}.consenti-modal--fullscreen{align-items:flex-start;justify-content:flex-start}.consenti-modal--fullscreen .consenti-modal__container{width:100vw;max-width:100vw;height:100dvh;max-height:100dvh;border-radius:0;margin:0}.consenti-modal__overlay{position:absolute;inset:0;background:var(--consenti-color-overlay)}.consenti-modal__container{position:relative;z-index:1;background:var(--consenti-color-bg);border-radius:var(--consenti-border-radius);box-shadow:var(--consenti-shadow);width:min(560px,90vw);max-height:85vh;display:flex;flex-direction:column;overflow:hidden}@media(prefers-reduced-motion: no-preference){.consenti-modal__container{animation:consenti-fade-in .15s ease-out}}.consenti-modal__header{display:flex;justify-content:space-between;padding:var(--consenti-spacing-md) var(--consenti-spacing-lg);border-bottom:1px solid var(--consenti-color-border);flex-shrink:0;position:relative;flex-direction:column}.consenti-modal__heading{font-size:var(--consenti-font-size-heading);font-weight:var(--consenti-font-weight-heading);margin:0;flex:1}.consenti-modal__header-controls{display:flex;align-items:center;gap:4px;flex-shrink:0;position:absolute;right:15px;top:10px}.consenti-modal__close{flex-shrink:0}.consenti-modal__links{display:flex;flex-wrap:wrap;gap:var(--consenti-spacing-sm);padding:var(--consenti-spacing-sm) var(--consenti-spacing-lg)}.consenti-modal__link{font-size:.85em;text-decoration:underline;text-underline-offset:2px}.consenti-modal__subheading{padding-top:var(--consenti-spacing-sm);margin:0;color:#6b7280;font-size:13px}.consenti-modal__text{padding:var(--consenti-spacing-sm) 0}.consenti-modal__body{overflow-y:auto;flex:1;padding:var(--consenti-spacing-sm) var(--consenti-spacing-lg);position:relative}.consenti-modal__categories{display:flex;flex-direction:column;gap:var(--consenti-spacing-md);padding:var(--consenti-spacing-md) 0}.consenti-modal__buttons{display:flex;flex-wrap:wrap;gap:var(--consenti-spacing-sm);padding:var(--consenti-spacing-md);border-top:1px solid var(--consenti-color-border);flex-shrink:0}.consenti-modal__receipt-option{padding-top:var(--consenti-spacing-md);border-top:1px solid var(--consenti-color-border);flex-shrink:0}.consenti-modal__receipt-label{display:flex;align-items:center;gap:var(--consenti-spacing-sm);font-size:var(--consenti-font-size-base);cursor:pointer}.consenti-modal__receipt-checkbox{width:16px;height:16px;flex-shrink:0;cursor:pointer}.consenti-modal__receipt-desc{margin:4px 0 0 24px;font-size:12px;color:#6b7280}.consenti-modal__powered-by{opacity:.5;color:var(--consenti-color-primary)}@keyframes consenti-fade-in{from{opacity:0;transform:scale(0.97)}to{opacity:1;transform:scale(1)}}.consenti-category{padding:var(--consenti-spacing-md) 0;border-bottom:1px solid var(--consenti-color-border)}.consenti-category:last-child{border-bottom:none}.consenti-category--mandatory .consenti-category__toggle{opacity:.6;cursor:default}.consenti-category__header{display:flex;align-items:center;justify-content:space-between;gap:var(--consenti-spacing-md);margin-bottom:var(--consenti-spacing-sm)}.consenti-category__heading{font-size:var(--consenti-font-size-heading);font-weight:var(--consenti-font-weight-heading);margin:0}.consenti-category__text{color:#949dab;font-size:13px;margin:0}.consenti-category__li-desc{margin:var(--consenti-spacing-sm) 0 0;font-size:12px;color:#6b7280;font-style:italic}.consenti-category__toggle{flex-shrink:0;position:relative;width:var(--consenti-toggle-width);height:var(--consenti-toggle-height);border-radius:calc(var(--consenti-toggle-height)/2);background:var(--consenti-toggle-bg-off);border:none;cursor:pointer;transition:background .2s ease;padding:0}.consenti-category__toggle:focus-visible{outline:2px solid var(--consenti-color-primary);outline-offset:2px}.consenti-category__toggle[aria-checked=true]{background:var(--consenti-toggle-bg-on)}.consenti-category__toggle--mandatory{cursor:default}.consenti-category__toggle-icon{position:absolute;top:50%;transform:translateY(-50%);width:10px;height:10px;display:flex;align-items:center;justify-content:center;pointer-events:none;transition:opacity .15s ease;color:hsla(0,0%,100%,.9)}.consenti-category__toggle-icon svg{width:10px;height:10px;overflow:visible}.consenti-category__toggle-icon--check{left:8px;opacity:0}[aria-checked=true] .consenti-category__toggle-icon--check{opacity:1}.consenti-category__toggle-icon--cross{right:8px;opacity:1}[aria-checked=true] .consenti-category__toggle-icon--cross{opacity:0}.consenti-category__toggle-knob{position:absolute;top:2px;left:2px;width:calc(var(--consenti-toggle-height) - 4px);height:calc(var(--consenti-toggle-height) - 4px);border-radius:50%;background:var(--consenti-toggle-knob);transition:transform .2s ease;display:block}[aria-checked=true] .consenti-category__toggle-knob{transform:translateX(calc(var(--consenti-toggle-width) - var(--consenti-toggle-height)))}.consenti-locale-switcher{position:relative;display:inline-flex}.consenti-locale-switcher__btn{font-size:1rem;line-height:1;cursor:pointer}.consenti-locale-switcher__list{position:absolute;top:calc(100% + 4px);right:0;z-index:calc(var(--consenti-z-modal) + 1);background:var(--consenti-color-bg);border:1px solid var(--consenti-color-border);border-radius:var(--consenti-border-radius);box-shadow:var(--consenti-shadow);list-style:none;margin:0;padding:4px 0;min-width:80px}.consenti-locale-switcher__list li{display:block}.consenti-locale-switcher__option{display:block;width:100%;padding:6px 12px;text-align:left;font-size:var(--consenti-font-size-base);color:var(--consenti-color-text);background:none;border:none;cursor:pointer}.consenti-locale-switcher__option:hover{background:var(--consenti-color-secondary)}.consenti-locale-switcher__option--active{font-weight:600;color:var(--consenti-color-primary)}.consenti-locale-switcher--modal .consenti-locale-switcher__list{right:0}
package/dist/index.d.ts CHANGED
@@ -1,14 +1,54 @@
1
+ type NonEmptyArray<T> = [T, ...T[]];
2
+ 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 ? {
3
+ [Key in keyof T]?: DeepPartial<T[Key]>;
4
+ } : T;
5
+
6
+ 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"];
7
+
8
+ /**
9
+ * Compliance-related literal types, derived from the regulation/group ID data
10
+ * that lives in @consenti/utils (packages/utils/src/compliance.ts).
11
+ *
12
+ * This import is type-only and fully erased at compile time — @consenti/types
13
+ * has no runtime dependency on @consenti/utils. The actual data (COMPLIANCE_GROUPS,
14
+ * EMBEDDED_COMPLIANCE_MAP, COMPLIANCE_VALIDATION_RULES, GPC_OPTIONS, etc.) lives in
15
+ * @consenti/utils; import it from there.
16
+ *
17
+ * HOW TO ADD A NEW REGULATION OR COMPLIANCE GROUP: see the header comment in
18
+ * packages/utils/src/compliance.ts — these types auto-update from that data.
19
+ */
20
+
21
+ type ComplianceGroupId = typeof COMPLIANCE_GROUP_IDS[number];
22
+
23
+ interface ComplianceMapData {
24
+ version: string;
25
+ countries: Record<string, CountryComplianceEntry>;
26
+ }
27
+ interface CountryComplianceEntry {
28
+ complianceGroup: ComplianceGroupId;
29
+ default: ComplianceGroupId;
30
+ description: string;
31
+ overriddenRegions?: Record<string, {
32
+ complianceGroup: ComplianceGroupId;
33
+ description: string;
34
+ }>;
35
+ }
36
+
1
37
  type ConsentStatus = 'granted' | 'denied' | 'objected';
2
38
  type ConsentAction = 'accept_all' | 'reject_all' | 'custom' | 'update';
3
39
  type ConsentValue = Record<string, ConsentStatus>;
4
40
  interface Cookie {
5
41
  id: string;
6
- type?: 'consent' | 'legitimate_interest';
42
+ legalBasis?: 'mandatory' | 'consent' | 'legitimate_interest';
43
+ /** @deprecated use legalBasis: 'mandatory' */
7
44
  mandatory?: boolean;
45
+ /** @deprecated use legalBasis */
46
+ type?: 'consent' | 'legitimate_interest';
8
47
  listenGpc?: boolean;
9
48
  expiry?: number;
10
49
  tcfVendorId?: number;
11
50
  tcfPurposes?: number[];
51
+ tcfSpecialFeatures?: number[];
12
52
  cpraCategory?: 'sale' | 'sharing' | 'sensitive';
13
53
  }
14
54
  type ButtonStyle = 'primary' | 'secondary' | 'text' | 'accent';
@@ -19,6 +59,7 @@ interface Button {
19
59
  action: ButtonAction;
20
60
  cookies?: string[] | '*' | '!';
21
61
  url?: string;
62
+ type?: string;
22
63
  }
23
64
  interface MainBanner {
24
65
  position: 'top' | 'bottom' | 'middle' | 'left-bottom' | 'right-bottom';
@@ -44,18 +85,13 @@ interface Category {
44
85
  };
45
86
  cookies: string[];
46
87
  }
47
- interface PreferenceModal {
88
+ interface PreferenceModal extends Omit<MainBanner, "position"> {
48
89
  position?: 'left' | 'right' | 'center';
49
- overlayOpacity?: number;
50
- showClose?: boolean;
51
- showLocaleSwitcher?: boolean;
52
- heading?: string;
53
- headingTag?: string;
54
90
  subheading?: string;
55
- htmlText?: string;
56
- buttons: Button[];
57
91
  categories: Category[];
58
92
  persistent?: boolean;
93
+ /** Screen width in px below which the modal expands to full screen. Default: 576. Set to 0 to disable. */
94
+ mobileFullScreenBreakpoint?: number;
59
95
  }
60
96
  interface LocaleTranslations {
61
97
  mainBanner: MainBanner;
@@ -72,25 +108,25 @@ interface DpdpaConfig {
72
108
  }
73
109
  interface LocaleTextContent {
74
110
  mainBanner: {
75
- heading: string;
111
+ heading?: string;
76
112
  htmlText: string;
77
- buttonLabels: string[];
113
+ buttonLabels?: string[];
78
114
  };
79
115
  gpcBanner: {
80
- heading: string;
116
+ heading?: string;
81
117
  htmlText: string;
82
- buttonLabels: string[];
118
+ buttonLabels?: string[];
83
119
  };
84
120
  preferenceModal: {
85
121
  heading: string;
86
122
  subheading?: string;
87
123
  htmlText?: string;
88
- buttonLabels: string[];
89
- categories: Array<{
124
+ buttonLabels?: string[];
125
+ categories: {
90
126
  id: string;
91
127
  heading: string;
92
128
  htmlText: string;
93
- }>;
129
+ }[];
94
130
  };
95
131
  }
96
132
  interface ProfileConfig {
@@ -106,9 +142,19 @@ interface ProfileConfig {
106
142
  cookieTemplateId?: string;
107
143
  uiTemplateId?: string;
108
144
  localeContents?: Record<string, LocaleTextContent>;
145
+ complianceGroup?: string;
146
+ /** @deprecated use gpcMode */
147
+ gpcCompliance?: 'strict' | 'honor' | 'ignore';
148
+ gpcMode?: 'ignore' | 'honor' | 'strict';
149
+ isActive?: boolean;
150
+ hidePoweredBy?: boolean;
151
+ allowReceipt?: boolean;
152
+ /** Per-compliance extra config (e.g. DPDPA data fiduciary name). */
153
+ complianceConfig?: Record<string, string>;
109
154
  }
110
155
  interface ResolvedProfile {
111
156
  id: number;
157
+ profileUuid?: string;
112
158
  version: number;
113
159
  defaultLocale: string;
114
160
  locales?: string[];
@@ -119,6 +165,26 @@ interface ResolvedProfile {
119
165
  cookieSigningKey?: string;
120
166
  dpdpa?: DpdpaConfig;
121
167
  darkMode?: boolean;
168
+ gpcMode?: 'ignore' | 'honor' | 'strict';
169
+ complianceGroup?: string;
170
+ complianceConfig?: Record<string, string>;
171
+ }
172
+ type WidgetCountryResolverFn = () => Promise<{
173
+ country: string | null;
174
+ region: string | null;
175
+ confidence: number;
176
+ }>;
177
+ interface AgeGateWidgetConfig {
178
+ enabled: boolean;
179
+ minimumAge: number;
180
+ requireParentalConsent?: boolean;
181
+ }
182
+ interface ComplianceWidgetConfig {
183
+ type?: 'auto' | ComplianceGroupId | string;
184
+ geoDataProvider?: 'default' | WidgetCountryResolverFn;
185
+ autoComplianceMap?: 'default' | ComplianceMapData | 'auto';
186
+ complianceMapUrl?: string;
187
+ ageGate?: AgeGateWidgetConfig;
122
188
  }
123
189
  interface GtmConfig {
124
190
  containerId?: string;
@@ -134,6 +200,9 @@ interface ApiConfig {
134
200
  enabled?: boolean;
135
201
  baseUrl?: string;
136
202
  authToken?: string;
203
+ tenantId?: string;
204
+ complianceGroup?: string;
205
+ trustDomain?: boolean;
137
206
  }
138
207
  interface ThemeConfig {
139
208
  bgColor?: string;
@@ -155,35 +224,52 @@ interface ThemeConfig {
155
224
  accentTextColor?: string;
156
225
  }
157
226
  interface CoreConfig {
158
- profileId?: number;
227
+ tenantId?: string;
159
228
  autoHonorGPC?: boolean | 'strict';
160
229
  locale?: string;
161
230
  disableCssTemplate?: boolean;
162
- signCookies?: boolean;
163
231
  cookieSigningKey?: string;
164
232
  allowReceipt?: boolean;
165
233
  cookieDomains?: string;
166
234
  storage?: 'cookie' | 'localStorage';
167
235
  theme?: ThemeConfig;
168
- privacyPolicyUrl?: string;
169
- regulation?: 'gdpr' | 'ccpa' | 'cpra' | 'dpdpa' | 'uk-gdpr' | 'lgpd' | 'pipeda' | 'popia' | 'pdpa-th' | 'appi' | 'kvkk';
170
236
  userId?: string;
237
+ usePrebuiltProfiles?: 'all' | NonEmptyArray<ComplianceGroupId>;
238
+ cacheResolvedProfiles?: boolean;
239
+ console?: Array<'info' | 'log' | 'warning' | 'error'>;
171
240
  }
172
241
  interface ConsentiConfig {
173
242
  core: CoreConfig;
243
+ compliance?: ComplianceWidgetConfig;
174
244
  rootEl?: string | HTMLElement;
175
245
  darkMode?: boolean;
176
246
  autoInit?: boolean;
177
247
  api?: ApiConfig;
178
248
  utils?: UtilsConfig;
179
249
  plugins?: ConsentiPlugin$1[];
180
- profileOverride?: Partial<ResolvedProfile>;
250
+ profileOverride?: DeepPartial<ResolvedProfile>;
251
+ /** When true, suppresses the "Powered by Consenti" footer link in the banner and modal. */
252
+ hidePoweredBy?: boolean;
181
253
  }
254
+ type ConsentiEventName = 'bannerInitialized' | 'consenti:bannerInitialized' | 'bannerVisibility' | 'consenti:bannerVisibility' | 'modalVisibility' | 'consenti:modalVisibility' | 'consentBeingSubmitted' | 'consenti:consentBeingSubmitted' | 'consentSubmitted' | 'consenti:consentSubmitted';
182
255
  interface ConsentiWidgetAPI {
183
256
  hasConsent(): boolean;
184
257
  getConsent(): ConsentValue | null;
185
258
  getConsentDate(): Date | false;
186
259
  getGTMConsent(): Record<string, string> | null;
260
+ isCookieGranted(cookieId: string, requestValue?: boolean): boolean | ConsentStatus;
261
+ isCategoryGranted(categoryId: string, requestValue?: boolean): boolean | {
262
+ [cookieId: string]: ConsentStatus;
263
+ }[];
264
+ grantAll(onlyMandatory?: boolean): Promise<void>;
265
+ denyAll(includingMandatory?: boolean): Promise<void>;
266
+ on(event: ConsentiEventName, handler: (data: ConsentEvent) => void): void;
267
+ off(event: ConsentiEventName, handler: (data: ConsentEvent) => void): void;
268
+ version(): {
269
+ package: string;
270
+ profileVersion: number | null;
271
+ consentVersion: number;
272
+ };
187
273
  bannerVisibility(): 'main' | 'gpc' | false;
188
274
  modalVisibility(): 'preference' | false;
189
275
  getProfile(): ResolvedProfile | null;
@@ -198,6 +284,10 @@ interface ConsentiWidgetAPI {
198
284
  getBannerElement(): HTMLElement | null;
199
285
  getModalElement(): HTMLElement | null;
200
286
  switchLocale(locale: string): void;
287
+ setDarkMode(enable?: boolean): void;
288
+ setTheme(theme: Partial<ThemeConfig>): void;
289
+ setConfig(config: DeepPartial<ConsentiConfig>): void;
290
+ setProfile(override: DeepPartial<ResolvedProfile>): void;
201
291
  init(): Promise<void>;
202
292
  onReady(callback: () => void): void;
203
293
  destroy(): void;
@@ -277,7 +367,7 @@ type ConsentiMessage = {
277
367
  * @example
278
368
  * ```ts
279
369
  * const widget = new ConsentiSetup({
280
- * core: { profileId: 0 },
370
+ * compliance: { type: 'opt-in' },
281
371
  * })
282
372
  * await widget.ready
283
373
  * console.log(widget.hasConsent())
@@ -289,6 +379,7 @@ declare class ConsentiSetup implements ConsentiWidgetAPI {
289
379
  private initialized;
290
380
  private initializing;
291
381
  private profile;
382
+ private resolvedBase;
292
383
  private consentStore;
293
384
  private eventBus;
294
385
  private banner;
@@ -302,6 +393,8 @@ declare class ConsentiSetup implements ConsentiWidgetAPI {
302
393
  private lastSignature;
303
394
  /** Tracks which banner variant is currently visible, or null when hidden. */
304
395
  private _bannerVariant;
396
+ /** Maps original handler functions to their DOM-wrapped listeners for on/off bookkeeping. */
397
+ private _eventHandlers;
305
398
  /**
306
399
  * Creates and initialises the consent widget.
307
400
  *
@@ -334,6 +427,8 @@ declare class ConsentiSetup implements ConsentiWidgetAPI {
334
427
  * @param invoke - Function that calls the desired hook method on a single plugin.
335
428
  */
336
429
  private callPluginHook;
430
+ private _buildGrantAllConsent;
431
+ private _buildDenyAllConsent;
337
432
  private buildHandlers;
338
433
  /**
339
434
  * Classifies a consent submission as `accept_all`, `reject_all`, `custom`, or `update`.
@@ -482,6 +577,93 @@ declare class ConsentiSetup implements ConsentiWidgetAPI {
482
577
  * @param locale - BCP 47 locale code to switch to (e.g. `'fr'`, `'de-AT'`).
483
578
  */
484
579
  switchLocale(locale: string): void;
580
+ /**
581
+ * Returns `true` if the named cookie's consent is `'granted'`.
582
+ * When `requestValue` is `true`, returns the raw `ConsentStatus` string instead of a boolean,
583
+ * or `false` if the cookie is not in the consent map.
584
+ */
585
+ isCookieGranted(cookieId: string, requestValue?: boolean): boolean | 'granted' | 'denied' | 'objected';
586
+ /**
587
+ * Checks consent for all cookies belonging to the given category.
588
+ *
589
+ * With `requestValue = false` (default): returns `true` only when every cookie in the
590
+ * category is `'granted'`, `false` otherwise.
591
+ *
592
+ * With `requestValue = true`: returns an array of one-key records — one per cookie in the
593
+ * category — e.g. `[{ analytics: 'granted' }, { pixel: 'denied' }]`.
594
+ *
595
+ * Returns `false` / `[]` if the category is not found or the widget is not yet initialized.
596
+ */
597
+ isCategoryGranted(categoryId: string, requestValue?: boolean): boolean | {
598
+ [cookieId: string]: 'granted' | 'denied' | 'objected';
599
+ }[];
600
+ /**
601
+ * Programmatically accepts cookies and hides the banner.
602
+ *
603
+ * @param onlyMandatory - When `true`, only mandatory cookies are granted and all others
604
+ * are set to `'denied'`. Defaults to `false` (grant everything).
605
+ */
606
+ grantAll(onlyMandatory?: boolean): Promise<void>;
607
+ /**
608
+ * Programmatically denies cookies and hides the banner.
609
+ *
610
+ * @param includingMandatory - When `true`, mandatory cookies are also denied. Defaults to
611
+ * `false` (mandatory cookies remain `'granted'`). Passing `true`
612
+ * logs a warning — ensure this is intentional.
613
+ */
614
+ denyAll(includingMandatory?: boolean): Promise<void>;
615
+ /**
616
+ * Subscribes to a Consenti widget event. The `consenti:` prefix is optional —
617
+ * both `'consentSubmitted'` and `'consenti:consentSubmitted'` are accepted.
618
+ *
619
+ * The same handler reference must be passed to `off()` to unsubscribe.
620
+ *
621
+ * @example
622
+ * ```ts
623
+ * const handler = (data) => console.log(data)
624
+ * widget.on('consentSubmitted', handler)
625
+ * // later:
626
+ * widget.off('consentSubmitted', handler)
627
+ * ```
628
+ */
629
+ on(event: ConsentiEventName, handler: (data: ConsentEvent) => void): void;
630
+ /**
631
+ * Removes a previously registered event handler. The same function reference passed to
632
+ * `on()` must be used here.
633
+ */
634
+ off(event: ConsentiEventName, handler: (data: ConsentEvent) => void): void;
635
+ /**
636
+ * Returns the current version of the package and the active profile/consent versions.
637
+ * Useful for debugging and support diagnostics.
638
+ */
639
+ version(): {
640
+ package: string;
641
+ profileVersion: number | null;
642
+ consentVersion: number;
643
+ };
644
+ /**
645
+ * Toggles or sets dark mode at runtime without re-initialising the widget.
646
+ * When `enable` is omitted, the current dark mode state is toggled.
647
+ */
648
+ setDarkMode(enable?: boolean): void;
649
+ /**
650
+ * Applies partial theme token overrides at runtime, merging them into the current theme.
651
+ * CSS custom properties on the root element are updated immediately.
652
+ */
653
+ setTheme(theme: Partial<ThemeConfig>): void;
654
+ /**
655
+ * Deep-merges a partial config into the current widget config.
656
+ * Theme and dark mode side-effects are re-applied immediately.
657
+ * Changes to locale or profile require an explicit `switchLocale()` or `init()` call.
658
+ */
659
+ setConfig(config: DeepPartial<ConsentiConfig>): void;
660
+ /**
661
+ * Merges a partial profile override into the current profile and re-renders any visible UI.
662
+ * Does not make a network request — the resolved base profile from the last `init()` is reused.
663
+ *
664
+ * No-op before `init()` completes (while `resolvedBase` is null).
665
+ */
666
+ setProfile(override: DeepPartial<ResolvedProfile>): void;
485
667
  /**
486
668
  * Destroys the widget — removes all DOM elements, disconnects the BroadcastChannel,
487
669
  * and calls `destroy()` on all registered plugins.
@@ -655,8 +837,12 @@ interface ConsentScriptOptions {
655
837
  widget: ConsentiSetup;
656
838
  /** Remote script `src` URL to inject when consent is granted. */
657
839
  src?: string;
658
- /** Inline JavaScript content to execute when consent is granted. */
659
- innerHTML?: string;
840
+ /**
841
+ * Inline JavaScript to inject when consent is granted (e.g. a GTM snippet).
842
+ * WARNING: this string is assigned directly to `script.innerHTML` — only
843
+ * pass a static, trusted literal. Never pass user-supplied or remote content.
844
+ */
845
+ unsafeInnerHTML?: string;
660
846
  /**
661
847
  * Additional HTML attributes applied to the injected `<script>` element.
662
848
  * Set a key to an empty string for boolean attributes (e.g. `{ async: '', defer: '' }`).
@@ -666,6 +852,12 @@ interface ConsentScriptOptions {
666
852
  onLoad?: () => void;
667
853
  /** Called after the `<script>` element has been removed from the DOM. */
668
854
  onRevoke?: () => void;
855
+ /**
856
+ * When `true` (default), the script is automatically removed when consent is revoked and
857
+ * re-injected when consent is re-granted. Set to `false` to evaluate consent only once at
858
+ * construction time — no event listener is attached and the script is never auto-removed.
859
+ */
860
+ bind?: boolean;
669
861
  }
670
862
  /**
671
863
  * Conditionally injects and removes a `<script>` element based on a specific
@@ -767,4 +959,4 @@ declare class CookieTrigger {
767
959
  destroy(): void;
768
960
  }
769
961
 
770
- export { type ApiConfig, type Button, type ButtonAction, type ButtonStyle, type Category, type ConsentAction, type ConsentEvent, type ConsentReceipt, type ConsentRecord, ConsentScript, type ConsentScriptOptions, type ConsentStatus, type ConsentValue, type ConsentiConfig, type ConsentiMessage, ConsentiPlugin, ConsentiProfile, ConsentiSetup, type ConsentiWidgetAPI, type Cookie, type CookieOptions, CookieTrigger, type CookieTriggerOptions, type CoreConfig, type GpcBanner, type GtmConfig, type LocaleTranslations, type MainBanner, type ParsedConsent, type PreferenceModal, type ProfileConfig, type ProfileTranslations, type ResolvedProfile, type ThemeConfig, type UtilsConfig };
962
+ export { type ApiConfig, type Button, type ButtonAction, type ButtonStyle, type Category, type ConsentAction, type ConsentEvent, type ConsentReceipt, type ConsentRecord, ConsentScript, type ConsentScriptOptions, type ConsentStatus, type ConsentValue, type ConsentiConfig, type ConsentiEventName, type ConsentiMessage, ConsentiPlugin, ConsentiProfile, ConsentiSetup, type ConsentiWidgetAPI, type Cookie, type CookieOptions, CookieTrigger, type CookieTriggerOptions, type CoreConfig, type GpcBanner, type GtmConfig, type LocaleTranslations, type MainBanner, type ParsedConsent, type PreferenceModal, type ProfileConfig, type ProfileTranslations, type ResolvedProfile, type ThemeConfig, type UtilsConfig };