@ecomconsult/consentkit 0.5.10 → 0.5.12
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/README.md +336 -68
- package/npm/index.d.ts +6 -0
- package/npm/internal-stub.mjs +1 -1
- package/package.json +1 -1
- package/src/ck-core.js +35 -3
- package/src/ck-locales.js +4 -0
- package/src/ck-ui.js +259 -69
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ConsentKit
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|

|
|
5
5
|

|
|
6
6
|

|
|
@@ -24,17 +24,18 @@ Vanilla ES2020, zero dependencies, no build step.
|
|
|
24
24
|
(e.g. a direct GA4 `gtag/js` tag) cannot be intercepted before the request
|
|
25
25
|
leaves — mark those up manually; the SaaS install check points at the exact tag
|
|
26
26
|
- **UI:** banner (`bar` / `box` / `modal`), preferences panel, floating re-open
|
|
27
|
-
button, light/dark,
|
|
27
|
+
button, light/dark, 34 locales
|
|
28
28
|
- **SSR-safe:** importing on the server never touches the DOM
|
|
29
29
|
- **Equal-weight buttons, no pre-ticked boxes** — the consent invariants are
|
|
30
|
-
fixed by design, see [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
30
|
+
fixed by design, see [CONTRIBUTING.md](https://github.com/vermoh/ConsentKit/blob/main/CONTRIBUTING.md)
|
|
31
31
|
|
|
32
|
-
> **Status: prototype (v0.
|
|
33
|
-
> browser
|
|
34
|
-
>
|
|
32
|
+
> **Status: prototype (v0.5.12).** The core, the UI and the demo are verified in
|
|
33
|
+
> a browser and covered by an automated suite (`npm test`); several distribution
|
|
34
|
+
> paths are not yet tested against live systems. See
|
|
35
|
+
> [Project status](#project-status) before shipping this to production.
|
|
35
36
|
|
|
36
37
|
**Не программист?** Пошаговая инструкция по-русски, с картинками и разбором по
|
|
37
|
-
кликам: **[INSTALL.ru.md](INSTALL.ru.md)**.
|
|
38
|
+
кликам: **[INSTALL.ru.md](https://github.com/vermoh/ConsentKit/blob/main/INSTALL.ru.md)**.
|
|
38
39
|
|
|
39
40
|
## Install
|
|
40
41
|
|
|
@@ -42,10 +43,11 @@ Four ways to add ConsentKit to a site, from simplest to most integrated.
|
|
|
42
43
|
|
|
43
44
|
| # | Method | Best for | Docs |
|
|
44
45
|
|---|---|---|---|
|
|
46
|
+
| 0 | **Prebuilt block** — copy one file from `ready/` into `<head>`, nothing to install | Tilda and other site builders; no developer needed | [`ready/README.md`](https://github.com/vermoh/ConsentKit/blob/main/ready/README.md) |
|
|
45
47
|
| 1 | **Script tags** — copy `src/` to your server, three `<script>` tags in `<head>` | Any site you control | [Quickstart below](#quickstart--script-tags) |
|
|
46
48
|
| 2 | **npm** — `npm install @ecomconsult/consentkit` | Bundled apps, React | [Quickstart below](#quickstart--npm) |
|
|
47
|
-
| 3 | **WordPress plugin** — copy the plugin folder to `wp-content/plugins/`; rewrites static tracker tags server-side | WordPress / WooCommerce | [`plugins/wordpress/consentkit/`](plugins/wordpress/consentkit
|
|
48
|
-
| 4 | **Google Tag Manager** — import the container, trigger tags on consent events | Sites already running GTM | [`integrations/gtm/README.md`](integrations/gtm/README.md) |
|
|
49
|
+
| 3 | **WordPress plugin** — copy the plugin folder to `wp-content/plugins/`; rewrites static tracker tags server-side | WordPress / WooCommerce | [`plugins/wordpress/consentkit/`](https://github.com/vermoh/ConsentKit/tree/main/plugins/wordpress/consentkit) |
|
|
50
|
+
| 4 | **Google Tag Manager** — import the container, trigger tags on consent events | Sites already running GTM | [`integrations/gtm/README.md`](https://github.com/vermoh/ConsentKit/blob/main/integrations/gtm/README.md) |
|
|
49
51
|
|
|
50
52
|
```sh
|
|
51
53
|
npm install @ecomconsult/consentkit
|
|
@@ -201,6 +203,7 @@ Pass any subset to `init()`. Nested objects merge with the defaults.
|
|
|
201
203
|
| `hostdb` | `Record<string, Category>` | — | Extra `host: category` pairs merged into the tracker database, applied before the initial scan. SaaS mode fills this from the service; `ConsentKit._extendHostDb()` does the same at any later point |
|
|
202
204
|
| `cookieTable` | `CkCookieTableEntry[]` | `[]` | Declared cookies, listed per category in the panel |
|
|
203
205
|
| `services` | `CkService[]` | `[]` | v0.5.8. Third-party services the site declares. Each gets its own toggle inside its category group in the panel, and can be refused individually — see [Services](#services). At most 50 |
|
|
206
|
+
| `branding` | `object` | absent | v0.3.5. The attribution line (and optional logo) at the foot of the banner, rendered by `src/ck-ui-branding.js`. Absent from the defaults: omit the key and nothing renders — see [Branding](#branding) |
|
|
204
207
|
|
|
205
208
|
`cookieTable` entries:
|
|
206
209
|
|
|
@@ -230,6 +233,26 @@ services: [{
|
|
|
230
233
|
}]
|
|
231
234
|
```
|
|
232
235
|
|
|
236
|
+
**How the list is laid out (v0.5.12).** A group's «N services · M cookies» line
|
|
237
|
+
is a disclosure **button**, and it is **collapsed by default** — eight services
|
|
238
|
+
no longer push the switches the visitor came for off the screen. Opening it
|
|
239
|
+
reveals the group's services and, at the end, the group's own «Which cookies
|
|
240
|
+
(N)» table. Each service is **one line**: name, vendor, and its switch on the
|
|
241
|
+
right. Everything else — the purpose, the privacy link, «Cookies it sets (N)» —
|
|
242
|
+
lives behind that row's own small «Details» disclosure. Both controls are real
|
|
243
|
+
focusable controls with a visible focus ring, and both are translated in ru, ro
|
|
244
|
+
and en (every other language falls back to en, as elsewhere).
|
|
245
|
+
|
|
246
|
+
A group with **no services** is unchanged from 0.5.7: no counter, no disclosure,
|
|
247
|
+
and its cookie table sits at the top level where it always did.
|
|
248
|
+
|
|
249
|
+
**A service in the `necessary` group gets no switch** (v0.5.12). That group
|
|
250
|
+
cannot be refused, so a control that could only ever sit at «off» would be
|
|
251
|
+
telling the visitor something untrue — the «always on» badge is rendered in its
|
|
252
|
+
place. `allowedService(id)` answers `true` for such a service unconditionally,
|
|
253
|
+
and a refusal for one is stripped from the denial map on every read and write,
|
|
254
|
+
including a record written by hand or by an older client.
|
|
255
|
+
|
|
233
256
|
What the toggle does:
|
|
234
257
|
|
|
235
258
|
- **Group off** — every service of that group is off and blocked, as before.
|
|
@@ -245,7 +268,8 @@ the block map under the row's own category, so a service host ConsentKit has
|
|
|
245
268
|
never heard of is still held back.
|
|
246
269
|
|
|
247
270
|
Refusals are stored in `ck_consent` as `services: { '<id>': false }` — denials
|
|
248
|
-
only. An id absent from the map is allowed, subject to its category.
|
|
271
|
+
only. An id absent from the map is allowed, subject to its category. A
|
|
272
|
+
`necessary` service is never in the map.
|
|
249
273
|
|
|
250
274
|
### Button appearance
|
|
251
275
|
|
|
@@ -281,8 +305,8 @@ border rule apply to *derived* values — the automatic text on a filled button,
|
|
|
281
305
|
the border and text an `outline` button takes from `theme.accent`, the link
|
|
282
306
|
colour read off the accent. A `fg`, `border` or `onAccent` you wrote yourself
|
|
283
307
|
is never repainted, however low it measures: the debug panel reports the ratio
|
|
284
|
-
and warns
|
|
285
|
-
yours. Concretely:
|
|
308
|
+
and warns that it is below the recommended floor (for example, "contrast 4.32 —
|
|
309
|
+
below the recommended 4.5"), and the choice stays yours. Concretely:
|
|
286
310
|
|
|
287
311
|
- a `fg` you set is painted as set and only measured against the fill behind
|
|
288
312
|
it; the *derived* text on a filled button still becomes white or `#161616`,
|
|
@@ -299,6 +323,13 @@ The card is `#ffffff` in light mode and `#1c1c1e` in dark. A colour the
|
|
|
299
323
|
arithmetic cannot read — a CSS colour name, an `rgb()` string — is left exactly
|
|
300
324
|
as you wrote it rather than being silently replaced.
|
|
301
325
|
|
|
326
|
+
**The preferences panel follows the banner, with no settings of its own**
|
|
327
|
+
(v0.5.11): the panel's «save choice» button is styled as `accept`, the panel's
|
|
328
|
+
own accept and reject as `settings`, and the floating button takes the
|
|
329
|
+
`accept` button's fill
|
|
330
|
+
(its border colour when `accept` is `outline`), while the category and service
|
|
331
|
+
switches keep `theme.accent`.
|
|
332
|
+
|
|
302
333
|
The same arithmetic is exposed as pure functions on `ConsentKit._contrast`
|
|
303
334
|
(`relativeLuminance`, `contrastRatio`, `ensureContrast`, `stepToContrast`,
|
|
304
335
|
`resolveButtonStyles`, `resolveRadius`, `resolveFont`, `resolveDetails`,
|
|
@@ -344,16 +375,16 @@ safe to call before the banner has mounted: a call that arrives while the UI
|
|
|
344
375
|
file is still loading is remembered and honoured on mount, so a link clicked
|
|
345
376
|
during a slow page load still works.
|
|
346
377
|
|
|
347
|
-
The same panel has an address.
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
378
|
+
The same panel also has an address. Any link to a page of the site ending in
|
|
379
|
+
`#ck-settings` opens the preferences panel — both when the page loads with that
|
|
380
|
+
fragment and when the link is followed on an already-open page. The fragment is
|
|
381
|
+
then removed from the address with `history.replaceState`, so a reload or a
|
|
382
|
+
«back» does not reopen the panel. This is the address the «change your cookie
|
|
383
|
+
choice» button on a cookie declaration page points at, and the one to put in a
|
|
384
|
+
site footer:
|
|
354
385
|
|
|
355
386
|
```html
|
|
356
|
-
<a href="#ck-settings"
|
|
387
|
+
<a href="#ck-settings">Change your cookie choice</a>
|
|
357
388
|
```
|
|
358
389
|
|
|
359
390
|
### Placeholders for blocked embeds
|
|
@@ -362,7 +393,7 @@ When the engine holds back an `<iframe>` before consent — a known tracker, or
|
|
|
362
393
|
any third-party frame in strict mode — the visitor would otherwise see an empty
|
|
363
394
|
hole where a video or a map should be. Since 0.5.7 ConsentKit draws a card in
|
|
364
395
|
its place: the name of the service, the category the embed is waiting for, a
|
|
365
|
-
primary button
|
|
396
|
+
primary button ("Allow and show") that grants **that one category** and
|
|
366
397
|
loads the embed, and a link to the full settings panel.
|
|
367
398
|
|
|
368
399
|
The card is sized from the frame's own `width`/`height` (or its computed size),
|
|
@@ -402,8 +433,9 @@ decide. Membership is a claim that a host delivers the site's own assets, not
|
|
|
402
433
|
that it is harmless in general — anything that *measures* keeps a real consent
|
|
403
434
|
category instead, which is why `static.cloudflareinsights.com` (Cloudflare Web
|
|
404
435
|
Analytics) is classified as `analytics` and blocked before consent even though
|
|
405
|
-
the rest of Cloudflare's CDN is infrastructure.
|
|
406
|
-
and returned as copies, so reading them cannot
|
|
436
|
+
the rest of Cloudflare's CDN is infrastructure. The list holds **37 entries**.
|
|
437
|
+
Both lists are matched by suffix and returned as copies, so reading them cannot
|
|
438
|
+
widen what strict mode allows.
|
|
407
439
|
|
|
408
440
|
## API
|
|
409
441
|
|
|
@@ -442,6 +474,44 @@ All methods are safe to call at any time and never throw.
|
|
|
442
474
|
Already-loaded scripts are not unloaded by `withdraw()` — cookies are cleared
|
|
443
475
|
and the next page load is clean.
|
|
444
476
|
|
|
477
|
+
### Introspection
|
|
478
|
+
|
|
479
|
+
Members prefixed with `_` are **not** private-by-convention placeholders: they
|
|
480
|
+
are a deliberate read-only surface for tooling — the debug panel, the hosted
|
|
481
|
+
cabinet's theme editor, and tests — and they are documented because those
|
|
482
|
+
consumers depend on them. They are stable within a minor version, and every one
|
|
483
|
+
of them returns a copy, so reading can never widen what the engine allows.
|
|
484
|
+
|
|
485
|
+
| Member | Returns | Description |
|
|
486
|
+
|---|---|---|
|
|
487
|
+
| `_blocked()` | `array` | What the engine is currently holding back, plus a sweep of blocked markup. Drives the debug panel's list |
|
|
488
|
+
| `_categoryForUrl(url)` | `string \| null` | The category the database gives a URL — the same lookup the engine uses |
|
|
489
|
+
| `_categories` | `string[]` | The four category names, as a copy |
|
|
490
|
+
| `_services()` | `array` | The normalised service rows from the config |
|
|
491
|
+
| `_serviceForUrl(url)` | `object \| null` | Which declared service a URL belongs to |
|
|
492
|
+
| `_deniedServices()` | `string[]` | Ids the visitor refused individually |
|
|
493
|
+
| `_extendHostDb(map)` | `number` | Merge extra `host: category` pairs; returns how many were accepted — see [Extending the tracker database](#extending-the-tracker-database) |
|
|
494
|
+
| `_infra()` | `string[]` | The 37 infrastructure hosts, as a copy |
|
|
495
|
+
| `_isInfra(url)` | `boolean` | Is this URL or hostname infrastructure? |
|
|
496
|
+
| `_baseAllow` | `object` | The built-in strict-mode allowlist (hosts plus path-scoped entries), as a copy |
|
|
497
|
+
|
|
498
|
+
`ConsentKit._contrast`, published by `src/ck-ui.js`, exposes the theme
|
|
499
|
+
arithmetic as pure functions so a theme editor can show exactly the numbers the
|
|
500
|
+
banner paints rather than reimplementing them. Nothing in it touches the DOM, so
|
|
501
|
+
it is safe to call in Node:
|
|
502
|
+
|
|
503
|
+
| Group | Functions |
|
|
504
|
+
|---|---|
|
|
505
|
+
| Colour maths | `relativeLuminance`, `contrastRatio`, `ensureContrast`, `stepToContrast` |
|
|
506
|
+
| Resolution | `resolveButtonStyles`, `resolveRadius`, `resolveFont`, `pickPageFont`, `resolveDetails`, `buildThemeCss` |
|
|
507
|
+
| Font probing | `nextProbeDelay`, `shouldReprobe` |
|
|
508
|
+
| Placeholders | `placeholderText`, `placeholdersEnabled` |
|
|
509
|
+
| Services panel | `cookieRowsForService`, `looseCookies`, `servicePurpose`, `groupCountLabel`, `serviceSignature`, `signature` |
|
|
510
|
+
| Wording | `plural`, `pluralIndex`, `buildStrings`, `localeTable`, `resolveLang` |
|
|
511
|
+
|
|
512
|
+
`ConsentKit._resolvePageFont()` reports the font family the banner resolved from
|
|
513
|
+
the page.
|
|
514
|
+
|
|
445
515
|
## Events
|
|
446
516
|
|
|
447
517
|
All are `CustomEvent` on `document`, with the payload in `detail`.
|
|
@@ -464,6 +534,20 @@ document.addEventListener('ck:change', (e) => {
|
|
|
464
534
|
The core never touches the UI directly; it only dispatches these events, and the
|
|
465
535
|
UI layer only calls the public API.
|
|
466
536
|
|
|
537
|
+
### dataLayer events (GTM)
|
|
538
|
+
|
|
539
|
+
Separately from the DOM events above, `integrations.gtmDataLayer` (on by
|
|
540
|
+
default) pushes to `window.dataLayer`, which is what GTM triggers listen to:
|
|
541
|
+
|
|
542
|
+
| Push | When |
|
|
543
|
+
|---|---|
|
|
544
|
+
| `ck_consent_update` with `ck_consent: { necessary, functional, analytics, marketing }` and `ck_method` | Every decision, including `withdraw()` |
|
|
545
|
+
| `ck_consent_functional` / `ck_consent_analytics` / `ck_consent_marketing` | Once per granted category, on the decision and again on a return visit when stored consent is restored. Each fires at most once per page |
|
|
546
|
+
|
|
547
|
+
The per-category events exist so a GTM tag can trigger on exactly the category
|
|
548
|
+
it needs without reading the payload. See
|
|
549
|
+
[`integrations/gtm/README.md`](https://github.com/vermoh/ConsentKit/blob/main/integrations/gtm/README.md).
|
|
550
|
+
|
|
467
551
|
## Blocking trackers
|
|
468
552
|
|
|
469
553
|
### Manual markup
|
|
@@ -511,9 +595,23 @@ document.head.appendChild(s);
|
|
|
511
595
|
```
|
|
512
596
|
|
|
513
597
|
Blocked elements are marked `data-ck-blocked` and their URL is remembered, so
|
|
514
|
-
granting consent later loads them without a reload.
|
|
515
|
-
|
|
516
|
-
and
|
|
598
|
+
granting consent later loads them without a reload.
|
|
599
|
+
|
|
600
|
+
The database ships **116 hosts** and **11 path rules**, matched by suffix (a
|
|
601
|
+
bare registrable domain also covers its subdomains) and by substring
|
|
602
|
+
respectively:
|
|
603
|
+
|
|
604
|
+
| Table | Entries | By category |
|
|
605
|
+
|---|---|---|
|
|
606
|
+
| `HOST_DB` | 116 | 40 `functional`, 32 `marketing`, 31 `analytics`, 13 `necessary` |
|
|
607
|
+
| `PATH_DB` | 11 | 4 `marketing`, 3 `functional`, 2 `analytics`, 2 `necessary` |
|
|
608
|
+
| `INFRA_DB` | 37 | not a category — see [Infrastructure](#infrastructure) |
|
|
609
|
+
|
|
610
|
+
Recognised hosts include Google Analytics, Facebook, Yandex Metrica, Hotjar,
|
|
611
|
+
TikTok and DoubleClick. The GTM **container** is deliberately not blocked (the
|
|
612
|
+
tags inside it obey Consent Mode); `/gtag/js` is blocked by path instead. The
|
|
613
|
+
same tables are exported to the WordPress plugin, so server and browser
|
|
614
|
+
classify a host identically.
|
|
517
615
|
|
|
518
616
|
Because the patches install at parse time, `ck-core.js` must load before any
|
|
519
617
|
tracker — put it first in `<head>` and do not add `defer`.
|
|
@@ -601,8 +699,8 @@ Two limits are worth stating plainly:
|
|
|
601
699
|
|
|
602
700
|
Runtime injection is covered by the patches above. A tracker tag written
|
|
603
701
|
**directly into the HTML** is not: the parser starts that request before the
|
|
604
|
-
first line of `ck-core.js` runs. The gap was measured (debt
|
|
605
|
-
14 ms, our script at 18 ms) and it is negative — no client-side technique
|
|
702
|
+
first line of `ck-core.js` runs. The gap was measured (debt D9 in SPEC.md:
|
|
703
|
+
request at 14 ms, our script at 18 ms) and it is negative — no client-side technique
|
|
606
704
|
closes it. Such tags need either manual markup, or a server that rewrites them
|
|
607
705
|
before the page is sent.
|
|
608
706
|
|
|
@@ -643,10 +741,38 @@ Outside WordPress the same idea applies to any server-side template: emit the
|
|
|
643
741
|
|
|
644
742
|
## Google Consent Mode v2
|
|
645
743
|
|
|
646
|
-
With `integrations.gcm` (default), the core pushes `consent: default`
|
|
647
|
-
|
|
648
|
-
`
|
|
649
|
-
|
|
744
|
+
With `integrations.gcm` (the default), the core pushes `consent: default` at
|
|
745
|
+
parse time — before any tag can load — with every signal `denied` and
|
|
746
|
+
`wait_for_update: 500`, then `consent: update` after each choice. Seven signals
|
|
747
|
+
are set, always as one block:
|
|
748
|
+
|
|
749
|
+
| Signal | Follows |
|
|
750
|
+
|---|---|
|
|
751
|
+
| `analytics_storage` | `analytics` |
|
|
752
|
+
| `ad_storage`, `ad_user_data`, `ad_personalization` | `marketing` |
|
|
753
|
+
| `functionality_storage`, `personalization_storage` | `functional` |
|
|
754
|
+
| `security_storage` | always `granted` |
|
|
755
|
+
|
|
756
|
+
`integrations.gtmDataLayer` (also on by default) is an independent gate: it
|
|
757
|
+
pushes a `ck_consent_update` event carrying `ck_consent` (the four categories)
|
|
758
|
+
and `ck_method`, so GTM triggers work even with `gcm: false`.
|
|
759
|
+
|
|
760
|
+
**What "denied" actually means.** Consent Mode is Google's own mechanism, not a
|
|
761
|
+
block: a Google tag that runs under denied signals sets **no cookies and no
|
|
762
|
+
identifiers**, but it still sends *cookieless pings* to Google, and those pings
|
|
763
|
+
carry the **page URL, the referrer and the user agent**, from an IP address
|
|
764
|
+
Google necessarily sees. That is enough for Google to see the
|
|
765
|
+
visit, and in the EU an IP address is personal data. Consent Mode alone is
|
|
766
|
+
therefore not the same as not being measured.
|
|
767
|
+
|
|
768
|
+
ConsentKit's blocking engine is the part that makes the difference: a tag it
|
|
769
|
+
holds back never runs at all, so it sends nothing — no ping, no URL, no IP. The
|
|
770
|
+
two work together, and Consent Mode is the fallback for the case the engine
|
|
771
|
+
cannot cover (a tag inside a GTM container, or a static `<script src>` the
|
|
772
|
+
parser requested before ConsentKit loaded — see
|
|
773
|
+
[Static tags](#static-tags-what-the-browser-cannot-catch)). If you need "nothing
|
|
774
|
+
reaches Google before consent", rely on the blocking engine and mark such tags
|
|
775
|
+
up; do not rely on Consent Mode by itself.
|
|
650
776
|
|
|
651
777
|
## Storage
|
|
652
778
|
|
|
@@ -690,36 +816,31 @@ hosting, the cabinet and the scanner — not the line itself.
|
|
|
690
816
|
For site builders that will not let you upload files, `ready/` holds
|
|
691
817
|
ready-to-paste `<script>` blocks — copy one wholesale into `<head>`. Zero
|
|
692
818
|
external requests. Rebuild them with `tools/build-inline.mjs` (see
|
|
693
|
-
[`tools/README.md`](tools/README.md)); each block's header records the exact
|
|
819
|
+
[`tools/README.md`](https://github.com/vermoh/ConsentKit/blob/main/tools/README.md)); each block's header records the exact
|
|
694
820
|
command that produced it.
|
|
695
821
|
|
|
696
|
-
ConsentKit 0.5.
|
|
697
|
-
this roughly
|
|
698
|
-
|
|
699
|
-
|
|
822
|
+
ConsentKit 0.5.12, rebuilt 2026-09-07, uncompressed — gzip on the server cuts
|
|
823
|
+
this roughly threefold. Every block includes the branding extension and the
|
|
824
|
+
attribution line; `--no-branding` drops both the code and the config and takes
|
|
825
|
+
**~26 KB** back off:
|
|
700
826
|
|
|
701
827
|
| Block | Languages | Bytes | gzip | `--no-branding` |
|
|
702
828
|
|---|---|---|---|---|
|
|
703
|
-
| `ready/en-bar.txt` | en |
|
|
704
|
-
| `ready/ru-bar.txt` | ru, ro, en |
|
|
705
|
-
| `ready/ru-box.txt` | ru, ro, en |
|
|
706
|
-
| `ready/ru-box-right.txt` | ru, ro, en |
|
|
707
|
-
| `ready/ru-modal.txt` | ru, ro, en |
|
|
708
|
-
| `ready/eu-bar.txt` | 34 languages |
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
allowlists, the public-suffix table) and the extensible tracker database.
|
|
716
|
-
|
|
717
|
-
Size is driven almost entirely by the bundled languages: `en` and `ru` are
|
|
718
|
-
built into the UI and cost nothing extra, while layout, position, theme and
|
|
719
|
-
accent change only a few bytes of config.
|
|
829
|
+
| `ready/en-bar.txt` | en | 264,816 | 82,798 | 238,438 |
|
|
830
|
+
| `ready/ru-bar.txt` | ru, ro, en | 266,806 | 83,673 | 240,242 |
|
|
831
|
+
| `ready/ru-box.txt` | ru, ro, en | 266,821 | 83,682 | 240,257 |
|
|
832
|
+
| `ready/ru-box-right.txt` | ru, ro, en | 266,830 | 83,687 | 240,266 |
|
|
833
|
+
| `ready/ru-modal.txt` | ru, ro, en | 266,814 | 83,679 | 240,250 |
|
|
834
|
+
| `ready/eu-bar.txt` | 34 languages | 315,460 | 101,998 | 288,916 |
|
|
835
|
+
|
|
836
|
+
The blocks are dominated by the core and the UI (roughly 97 KB and 129 KB of
|
|
837
|
+
source respectively, comments included — the builder concatenates the sources
|
|
838
|
+
as they are and does not minify). Bundled languages account for the rest:
|
|
839
|
+
`en` and `ru` are built into the UI and cost nothing extra, while layout,
|
|
840
|
+
position, theme and accent change only a few bytes of config.
|
|
720
841
|
|
|
721
842
|
The debug panel is **not** in these numbers. Blocks carry a ~5.1 KB loader
|
|
722
|
-
(`src/ck-debug-loader.js`) which fetches the
|
|
843
|
+
(`src/ck-debug-loader.js`) which fetches the ~54 KB panel only when someone opens
|
|
723
844
|
the page with `?ck_debug=1` — see [Debug mode](#debug-mode). An ordinary visitor
|
|
724
845
|
downloads the loader and nothing more.
|
|
725
846
|
|
|
@@ -762,7 +883,7 @@ a support ticket.
|
|
|
762
883
|
|
|
763
884
|
### How the panel gets onto the page
|
|
764
885
|
|
|
765
|
-
The panel is ~
|
|
886
|
+
The panel is ~54 KB, and on any given page exactly one person will ever open
|
|
766
887
|
it. So `ready/*.txt` and the WordPress plugin ship **`src/ck-debug-loader.js`**
|
|
767
888
|
(~5.1 KB) instead, and the loader fetches the panel on demand. With no flag set
|
|
768
889
|
the loader creates no DOM, installs no observers and makes **no network
|
|
@@ -871,6 +992,24 @@ When the configuration contains a `log` endpoint, each decision is POSTed with
|
|
|
871
992
|
via `sendBeacon` on `pagehide`. Withdrawals are sent with `method: "withdraw"`.
|
|
872
993
|
No other network requests are made.
|
|
873
994
|
|
|
995
|
+
The payload is a **closed schema** — anything outside this list is rejected by
|
|
996
|
+
the server as a 400:
|
|
997
|
+
|
|
998
|
+
| Field | Always? | Value |
|
|
999
|
+
|---|---|---|
|
|
1000
|
+
| `siteId`, `key` | yes | The site id and the log key from the config |
|
|
1001
|
+
| `cfg` | yes | Version of the config the decision was made under |
|
|
1002
|
+
| `id`, `ts` | yes | uuid and ISO timestamp of the record. A withdrawal gets a fresh pair |
|
|
1003
|
+
| `categories` | yes | Exactly three booleans: `functional`, `analytics`, `marketing`. `necessary` is not part of the schema |
|
|
1004
|
+
| `method` | yes | `accept_all` \| `reject_all` \| `custom` \| `withdraw` |
|
|
1005
|
+
| `lang`, `layout` | when resolved | The language and layout the visitor actually saw |
|
|
1006
|
+
| `services` | only on `custom` | v0.5.8. The ids the visitor refused, sent only when the list is non-empty. Capped at 50 ids of at most 64 characters |
|
|
1007
|
+
|
|
1008
|
+
The client puts no page URL, referrer or user agent in the body. The request
|
|
1009
|
+
itself is still an ordinary HTTP request to the API host, so that host sees the
|
|
1010
|
+
connection's IP address like any server would — what the *payload* carries is
|
|
1011
|
+
the list above and nothing more.
|
|
1012
|
+
|
|
874
1013
|
To try it locally, a mock API is included:
|
|
875
1014
|
|
|
876
1015
|
```sh
|
|
@@ -878,10 +1017,130 @@ node demo/mock-api.mjs # http://localhost:8788
|
|
|
878
1017
|
# serve the repo root, then open demo/saas.html
|
|
879
1018
|
```
|
|
880
1019
|
|
|
1020
|
+
## Changelog
|
|
1021
|
+
|
|
1022
|
+
Client versions. The WordPress plugin tracks the same numbers and keeps its own
|
|
1023
|
+
notes in
|
|
1024
|
+
[`plugins/wordpress/consentkit/readme.txt`](https://github.com/vermoh/ConsentKit/blob/main/plugins/wordpress/consentkit/readme.txt).
|
|
1025
|
+
|
|
1026
|
+
### 0.5.12
|
|
1027
|
+
- **Services in the preferences panel are collapsed by default and compact.**
|
|
1028
|
+
The group's «N services · M cookies» line became a disclosure button; opening
|
|
1029
|
+
it reveals the services and the group's cookie table. Each service row is one
|
|
1030
|
+
line — name, vendor, switch — with the purpose, the privacy link and its
|
|
1031
|
+
cookie list behind a small «Details» disclosure inside the row.
|
|
1032
|
+
- **A service in the «necessary» group has no switch.** It carries the «always
|
|
1033
|
+
on» badge instead, `allowedService()` is unconditionally `true` for it, and a
|
|
1034
|
+
refusal for such a service is stripped from the denial map on read and write.
|
|
1035
|
+
- Tracker database: `c.bing.com`.
|
|
1036
|
+
|
|
1037
|
+
### 0.5.11
|
|
1038
|
+
- The preferences panel and the floating button follow the **banner's** buttons
|
|
1039
|
+
by role, with no theme settings of their own: «save choice» is styled as
|
|
1040
|
+
`accept`, the panel's own accept and reject as `settings`, and the floating
|
|
1041
|
+
button takes the `accept` fill (its border colour when `accept` is `outline`).
|
|
1042
|
+
|
|
1043
|
+
### 0.5.10
|
|
1044
|
+
- **A colour the site owner set is painted as set.** The 4.5:1 text rule and the
|
|
1045
|
+
3:1 border rule now correct only *derived* colours; an explicit `fg`, `border`
|
|
1046
|
+
or `onAccent` is left alone however low it measures.
|
|
1047
|
+
- `theme.light.onAccent` — the light-mode mirror of `theme.dark.onAccent`.
|
|
1048
|
+
- The debug panel reports the measured ratio and warns, instead of claiming a
|
|
1049
|
+
value was fixed automatically.
|
|
1050
|
+
|
|
1051
|
+
### 0.5.9
|
|
1052
|
+
- Tracker database: +42 entries — YouTube, Vimeo, Facebook and Instagram embeds,
|
|
1053
|
+
chats and CRM (Freshworks, Viber, Telegram, Bitrix24, amoCRM), forms and
|
|
1054
|
+
scheduling (Calendly, Typeform), payments (Stripe, PayPal, paynet.md, MAIB),
|
|
1055
|
+
Sentry, 999.md. Vercel and Netlify are classified as infrastructure.
|
|
1056
|
+
- The debug panel is honest about Consent Mode: "no cookies, but the page
|
|
1057
|
+
address and browser type are sent".
|
|
1058
|
+
|
|
1059
|
+
### 0.5.8
|
|
1060
|
+
- **Services** (`services`): a site declares individual third parties, each of
|
|
1061
|
+
which gets its own toggle inside its category group in the panel, with its own
|
|
1062
|
+
vendor, purpose, privacy link and cookies. A visitor can accept a category and
|
|
1063
|
+
still refuse one service; the refusal survives the group being switched off
|
|
1064
|
+
and on again. Refusals are stored in `ck_consent` as `services: { id: false }`
|
|
1065
|
+
and, in SaaS mode, reported in the beacon's `services` field.
|
|
1066
|
+
- `ConsentKit.allowedService(id)`.
|
|
1067
|
+
|
|
1068
|
+
### 0.5.7
|
|
1069
|
+
- Placeholders for blocked embeds: a card in place of a held-back video or map,
|
|
1070
|
+
with an "Allow and show" button that grants just that category
|
|
1071
|
+
(`blocking.placeholders`, on by default).
|
|
1072
|
+
- `#ck-settings` in a page address opens the preferences panel;
|
|
1073
|
+
`ConsentKit.openSettings()` is safe to call before the UI has mounted.
|
|
1074
|
+
- `texts.detailsAction: "declaration"` and `texts.declarationUrl`.
|
|
1075
|
+
|
|
1076
|
+
### 0.5.6
|
|
1077
|
+
- The attribution line follows the banner's language
|
|
1078
|
+
(`branding.poweredBy.texts`).
|
|
1079
|
+
|
|
1080
|
+
### 0.5.5
|
|
1081
|
+
- Banner and panel texts rewritten in plain language (ru, ro, en).
|
|
1082
|
+
|
|
1083
|
+
### 0.5.4
|
|
1084
|
+
- Database: Google Maps is `functional`; Tilda platform services and
|
|
1085
|
+
`fonts.google.com` are infrastructure; Google Ads pings
|
|
1086
|
+
(`/pagead/1p-user-list`, `/ads/ga-audiences`) are `marketing`.
|
|
1087
|
+
|
|
1088
|
+
### 0.5.3
|
|
1089
|
+
- The banner re-resolves the page font after full load (on Tilda a reload from
|
|
1090
|
+
cache left the banner in Times), and falls back to the system stack when the
|
|
1091
|
+
page font cannot be determined.
|
|
1092
|
+
|
|
1093
|
+
### 0.5.2
|
|
1094
|
+
- The banner takes its font from the page's real text rather than from `body`.
|
|
1095
|
+
|
|
1096
|
+
### 0.5.1
|
|
1097
|
+
- Database: general CDNs (gstatic.com, aspnetcdn.com, kxcdn.com, jsDelivr,
|
|
1098
|
+
cdnjs, unpkg, CloudFront and others) are infrastructure, not trackers; Google
|
|
1099
|
+
reCAPTCHA is `necessary`; Searchanise and iuteCredit are `functional`.
|
|
1100
|
+
|
|
1101
|
+
### 0.5.0
|
|
1102
|
+
- **The theme engine.** `theme.font` (the banner takes the site's font by
|
|
1103
|
+
default, `'system'` restores the old stack), `theme.radius: { card, button }`
|
|
1104
|
+
in px, and `theme.buttons` — variant, background, text, border and border
|
|
1105
|
+
width per button. Accept and reject are always equal in size, weight and
|
|
1106
|
+
variant, by construction. Contrast is checked automatically: text below 4.5:1
|
|
1107
|
+
and borders below 3:1 are corrected.
|
|
1108
|
+
- `texts.policyUrl` and `texts.detailsAction` (`policy` / `settings` / `hide`):
|
|
1109
|
+
«Learn more» now does something. Before 0.5.0 it was `<a href="#">` with no
|
|
1110
|
+
handler — any site on 0.4.x or earlier has a dead link.
|
|
1111
|
+
- The corner card gained its reference geometry: up to 540px wide, 24px padding,
|
|
1112
|
+
buttons in a row with an 8px gap and stacked on narrow screens.
|
|
1113
|
+
|
|
1114
|
+
### 0.4.1
|
|
1115
|
+
- Infrastructure list (`ConsentKit._infra()`): builder CDNs, Google Fonts and
|
|
1116
|
+
captcha, never intercepted by strict mode.
|
|
1117
|
+
`static.cloudflareinsights.com` is deliberately excluded — it is analytics.
|
|
1118
|
+
|
|
1119
|
+
### 0.4.0
|
|
1120
|
+
- **Strict mode** (`blocking.mode: 'strict'`): before consent, any third-party
|
|
1121
|
+
script or iframe that is not same-site, allow-listed or in the built-in
|
|
1122
|
+
allowlist is held back and filed as `marketing`.
|
|
1123
|
+
- `ConsentKit._extendHostDb()` and the `hostdb` config key.
|
|
1124
|
+
- `<iframe src>` is intercepted, not just scripts.
|
|
1125
|
+
|
|
1126
|
+
### 0.3.6
|
|
1127
|
+
- Branding moved to `src/ck-ui-branding.js`, so `--no-branding` drops the code
|
|
1128
|
+
as well as the config (~26 KB off a block).
|
|
1129
|
+
|
|
1130
|
+
### 0.3.5
|
|
1131
|
+
- **Server-side tracker markup in the WordPress plugin, on by default** — the
|
|
1132
|
+
one case the browser engine cannot cover. The database shipped to PHP is
|
|
1133
|
+
generated from `src/ck-core.js`, and a test fails when the two drift.
|
|
1134
|
+
- The debug panel became a lazily-loaded file behind a ~5 KB loader.
|
|
1135
|
+
|
|
1136
|
+
### 0.3.2
|
|
1137
|
+
- The GTM container is no longer blocked: tags inside it obey Consent Mode, and
|
|
1138
|
+
blocking the container breaks that. `/gtag/js` is still blocked by path.
|
|
1139
|
+
|
|
881
1140
|
## Project status
|
|
882
1141
|
|
|
883
|
-
**This is a prototype (v0.
|
|
884
|
-
has been verified and what has not.
|
|
1142
|
+
**This is a prototype (v0.5.12), not a released product.** It is honest about
|
|
1143
|
+
what has been verified and what has not.
|
|
885
1144
|
|
|
886
1145
|
### Verified
|
|
887
1146
|
|
|
@@ -893,6 +1152,11 @@ has been verified and what has not.
|
|
|
893
1152
|
- `dataLayer` event trace for consent restore, upgrade and withdrawal.
|
|
894
1153
|
- npm entry points and TypeScript types: syntax and import smoke tests in Node
|
|
895
1154
|
without a DOM.
|
|
1155
|
+
- An automated suite runs under `npm test` (`node --test test/*.test.mjs`):
|
|
1156
|
+
blocking engine, tracker-database classification and its PHP export, services,
|
|
1157
|
+
the theme and contrast arithmetic, the settings panel, branding, the debug
|
|
1158
|
+
loader and panel, the generated site, and a version guard that fails when
|
|
1159
|
+
`package.json`, `src/ck-core.js` and the blocks in `ready/` drift apart.
|
|
896
1160
|
- PHP files of the WordPress plugin pass `php -l` on 7.4, 8.3 and 8.5.
|
|
897
1161
|
- The server-side rewriting engine has its own suite of 61 cases
|
|
898
1162
|
(`plugins/wordpress/consentkit/tests/rewrite.test.php`), green on PHP 7.4,
|
|
@@ -910,16 +1174,20 @@ has been verified and what has not.
|
|
|
910
1174
|
and structurally modelled on the documented export format, but Tag Manager has
|
|
911
1175
|
not accepted it in practice; some field names (notably GA4 config
|
|
912
1176
|
`measurementId` vs `tagId`) may need correction on first import.
|
|
913
|
-
- **
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1177
|
+
- **The 32 locales of the language pack are drafts.** Only `en` and `ru` — the
|
|
1178
|
+
two built into the UI — are authored rather than translated. The rest are
|
|
1179
|
+
usable but have not been reviewed by native speakers (the weakest are `mt`,
|
|
1180
|
+
`ga`, `is`, `sq`, `mk`; `pl` mixes politeness forms). Legal wording — "Reject
|
|
1181
|
+
all", "always active" — should be checked by someone who knows the local
|
|
1182
|
+
regulator's language before you rely on it.
|
|
917
1183
|
- **There is no server-side consent log.** Consent lives only in the visitor's
|
|
918
1184
|
browser (cookie plus `localStorage`). GDPR accountability may require you to
|
|
919
1185
|
be able to *demonstrate* that consent was given; that record-keeping is not
|
|
920
1186
|
part of this prototype and you would have to build it yourself.
|
|
921
|
-
- No
|
|
922
|
-
|
|
1187
|
+
- **No end-to-end browser tests and no CI beyond the Pages deployment.** The
|
|
1188
|
+
`npm test` suite runs in Node against DOM stubs, not a real browser; browser
|
|
1189
|
+
verification is still the manual smoke checklist in
|
|
1190
|
+
[CONTRIBUTING.md](https://github.com/vermoh/ConsentKit/blob/main/CONTRIBUTING.md).
|
|
923
1191
|
- Not audited by a lawyer. ConsentKit is a technical building block, not legal
|
|
924
1192
|
advice, and it cannot make a site compliant on its own — your privacy policy,
|
|
925
1193
|
your cookie inventory and your record-keeping are still yours.
|
|
@@ -927,7 +1195,7 @@ has been verified and what has not.
|
|
|
927
1195
|
### Contributing
|
|
928
1196
|
|
|
929
1197
|
Structure of the repository, the GDPR invariants that must not change, and how
|
|
930
|
-
to run the checks: [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
1198
|
+
to run the checks: [CONTRIBUTING.md](https://github.com/vermoh/ConsentKit/blob/main/CONTRIBUTING.md).
|
|
931
1199
|
|
|
932
1200
|
## License
|
|
933
1201
|
|
|
@@ -935,8 +1203,8 @@ Copyright (c) 2026 E-COM CONSULT PLUS.
|
|
|
935
1203
|
|
|
936
1204
|
| Part | Licence |
|
|
937
1205
|
|---|---|
|
|
938
|
-
| Client (`src/`), npm package, inline builder, demo | [MIT](LICENSE) |
|
|
939
|
-
| WordPress plugin (`plugins/wordpress/consentkit/`) | [GPL-2.0-or-later](plugins/wordpress/consentkit/LICENSE) |
|
|
1206
|
+
| Client (`src/`), npm package, inline builder, demo | [MIT](https://github.com/vermoh/ConsentKit/blob/main/LICENSE) |
|
|
1207
|
+
| WordPress plugin (`plugins/wordpress/consentkit/`) | [GPL-2.0-or-later](https://github.com/vermoh/ConsentKit/blob/main/plugins/wordpress/consentkit/LICENSE) |
|
|
940
1208
|
|
|
941
1209
|
The client is MIT so it can be embedded anywhere without licence friction. The
|
|
942
1210
|
WordPress plugin ships under GPLv2+ because the WordPress ecosystem effectively
|
|
@@ -944,4 +1212,4 @@ requires it; MIT permits the plugin to bundle copies of the client in its
|
|
|
944
1212
|
`assets/` directory.
|
|
945
1213
|
|
|
946
1214
|
Contributions require a `Signed-off-by` line (DCO) — see
|
|
947
|
-
[CONTRIBUTING.md](CONTRIBUTING.md).
|
|
1215
|
+
[CONTRIBUTING.md](https://github.com/vermoh/ConsentKit/blob/main/CONTRIBUTING.md).
|
package/npm/index.d.ts
CHANGED
|
@@ -141,6 +141,12 @@ export interface CkThemeConfig {
|
|
|
141
141
|
mode?: 'auto' | 'light' | 'dark';
|
|
142
142
|
/** v0.2. Overrides the built-in dark palette. */
|
|
143
143
|
dark?: CkDarkTheme;
|
|
144
|
+
/**
|
|
145
|
+
* v0.5.10. The light-mode mirror of `dark`, for one value: an `onAccent` the
|
|
146
|
+
* owner set for the filled buttons' text in light mode. A typed value is
|
|
147
|
+
* painted as typed (contrast is a warning, never a correction).
|
|
148
|
+
*/
|
|
149
|
+
light?: { onAccent?: string };
|
|
144
150
|
}
|
|
145
151
|
|
|
146
152
|
/** v0.5.0 (SPEC V1.6 §2). Copy and the «Learn more» control. */
|
package/npm/internal-stub.mjs
CHANGED
|
@@ -28,7 +28,7 @@ export function undecidedState() {
|
|
|
28
28
|
*/
|
|
29
29
|
export function createStub() {
|
|
30
30
|
const stub = {
|
|
31
|
-
version: '0.5.
|
|
31
|
+
version: '0.5.12',
|
|
32
32
|
config: {},
|
|
33
33
|
init: function () { return undecidedState(); },
|
|
34
34
|
allowed: function (cat) { return cat === 'necessary'; },
|
package/package.json
CHANGED
package/src/ck-core.js
CHANGED
|
@@ -108,6 +108,7 @@
|
|
|
108
108
|
'ct.pinterest.com': 'marketing', // subdomain: pinterest.com is a normal site
|
|
109
109
|
'sc-static.net': 'marketing', // Snapchat pixel CDN
|
|
110
110
|
'bat.bing.com': 'marketing', // subdomain: bing.com is a normal site
|
|
111
|
+
'c.bing.com': 'marketing', // Bing pixel (/c.gif) fired by UET and Clarity
|
|
111
112
|
'adroll.com': 'marketing',
|
|
112
113
|
'hs-analytics.net': 'marketing', // HubSpot tracking
|
|
113
114
|
'hs-scripts.com': 'marketing',
|
|
@@ -796,13 +797,26 @@
|
|
|
796
797
|
// key that is not a service id — is dropped rather than trusted: this record
|
|
797
798
|
// is attacker-writable (it lives in a cookie and in localStorage), and a
|
|
798
799
|
// malformed entry must not be able to widen or narrow what gets blocked.
|
|
800
|
+
//
|
|
801
|
+
// 0.5.12 — a service in the `necessary` group is ALSO dropped, on read and on
|
|
802
|
+
// write alike. Such a service has no switch in the panel (the group is always
|
|
803
|
+
// on, so a control that could only ever refuse it would be a lie), and
|
|
804
|
+
// allowedService() answers `true` for it unconditionally — so a denial for one
|
|
805
|
+
// could only ever be dead weight that survives in the record and confuses the
|
|
806
|
+
// beacon and the debug report. The filter is safe here because init() runs
|
|
807
|
+
// buildServices() BEFORE loadRecord(), so SERVICE_BY_ID is already populated
|
|
808
|
+
// when a stored map is read back; an id the registry does not know is left
|
|
809
|
+
// alone, exactly as before, because nothing can be claimed about its category.
|
|
799
810
|
function readServices(raw) {
|
|
800
811
|
var out = {};
|
|
801
812
|
if (!isPlainObject(raw)) { return out; }
|
|
802
813
|
var keys = Object.keys(raw);
|
|
803
814
|
for (var i = 0; i < keys.length && i < SERVICE_MAX; i++) {
|
|
804
815
|
var k = keys[i];
|
|
805
|
-
if (raw[k]
|
|
816
|
+
if (raw[k] !== false || !SERVICE_ID_RE.test(k)) { continue; }
|
|
817
|
+
var s = SERVICE_BY_ID[k];
|
|
818
|
+
if (s && s.category === 'necessary') { continue; }
|
|
819
|
+
out[k] = false;
|
|
806
820
|
}
|
|
807
821
|
return out;
|
|
808
822
|
}
|
|
@@ -1201,6 +1215,14 @@
|
|
|
1201
1215
|
// silent block of something the visitor was never asked about.
|
|
1202
1216
|
function serviceDenied(id) {
|
|
1203
1217
|
if (!id || typeof id !== 'string') { return false; }
|
|
1218
|
+
/* 0.5.12 — a service in the `necessary` group is NEVER denied, whatever the
|
|
1219
|
+
map says. readServices() already strips such an entry on every read and
|
|
1220
|
+
write, so this is the single choke point that also covers a map mutated
|
|
1221
|
+
by some other path: deniedForSrc() (the blocking patches), and
|
|
1222
|
+
deniedServiceIds() (the beacon field and the debug report) both ask this
|
|
1223
|
+
question, so answering it once here keeps the three from drifting. */
|
|
1224
|
+
var s = SERVICE_BY_ID[id];
|
|
1225
|
+
if (s && s.category === 'necessary') { return false; }
|
|
1204
1226
|
return state.services[id] === false;
|
|
1205
1227
|
}
|
|
1206
1228
|
|
|
@@ -1212,6 +1234,13 @@
|
|
|
1212
1234
|
function allowedService(id) {
|
|
1213
1235
|
var s = SERVICE_BY_ID[id];
|
|
1214
1236
|
if (!s) { return true; } // unknown id: nothing to withhold
|
|
1237
|
+
/* 0.5.12 — a `necessary` service is allowed, full stop. The group cannot be
|
|
1238
|
+
switched off (allowed('necessary') is always true), the panel renders no
|
|
1239
|
+
switch for it, and readServices() drops any denial that reaches the map.
|
|
1240
|
+
This is the belt to that braces: a denial arriving by some path neither
|
|
1241
|
+
covers — a stale in-memory map, a caller reaching past accept() — must
|
|
1242
|
+
still not be able to hold back something the visitor cannot re-enable. */
|
|
1243
|
+
if (s.category === 'necessary') { return true; }
|
|
1215
1244
|
if (!allowed(s.category)) { return false; }
|
|
1216
1245
|
return !serviceDenied(id);
|
|
1217
1246
|
}
|
|
@@ -1229,7 +1258,10 @@
|
|
|
1229
1258
|
function deniedServiceIds() {
|
|
1230
1259
|
var out = [];
|
|
1231
1260
|
for (var i = 0; i < SERVICES.length; i++) {
|
|
1232
|
-
|
|
1261
|
+
// Through serviceDenied(), not the raw map: that is the one place the
|
|
1262
|
+
// `necessary` rule lives, so the beacon and the debug report can never
|
|
1263
|
+
// name a service the blocking engine is in fact allowing.
|
|
1264
|
+
if (serviceDenied(SERVICES[i].id)) { out.push(SERVICES[i].id); }
|
|
1233
1265
|
}
|
|
1234
1266
|
return out;
|
|
1235
1267
|
}
|
|
@@ -1941,7 +1973,7 @@
|
|
|
1941
1973
|
// Public API
|
|
1942
1974
|
// ---------------------------------------------------------------------------
|
|
1943
1975
|
var ConsentKit = {
|
|
1944
|
-
version: '0.5.
|
|
1976
|
+
version: '0.5.12',
|
|
1945
1977
|
config: config,
|
|
1946
1978
|
|
|
1947
1979
|
init: function (userConfig) {
|
package/src/ck-locales.js
CHANGED
|
@@ -584,6 +584,10 @@
|
|
|
584
584
|
ckCount: ['{n} cookie', '{n} cookie-uri', '{n} de cookie-uri'],
|
|
585
585
|
svcPolicy: 'Politica',
|
|
586
586
|
svcCookies: 'Ce cookie-uri pune',
|
|
587
|
+
// 0.5.12 — dezvăluirea din rând. NU `more`, care aici este o propoziție
|
|
588
|
+
// («Aflați mai multe»), nu un control de rând.
|
|
589
|
+
svcDetails: 'Detalii',
|
|
590
|
+
svcListLabel: 'Servicii și cookie-uri din acest grup',
|
|
587
591
|
cat: {
|
|
588
592
|
necessary: { title: 'Necesare', desc: 'Sunt necesare pentru funcționarea site-ului: autentificare, securitate, reținerea alegerii dumneavoastră. Nu pot fi dezactivate.' },
|
|
589
593
|
functional: { title: 'Funcționale', desc: 'Rețin alegerile dumneavoastră: limba, coșul, chatul.' },
|
package/src/ck-ui.js
CHANGED
|
@@ -44,6 +44,14 @@
|
|
|
44
44
|
// The service's own cookies, listed under it rather than in the group's
|
|
45
45
|
// «Which cookies» table.
|
|
46
46
|
svcCookies: 'Cookies it sets',
|
|
47
|
+
// 0.5.12 — the per-row disclosure. A row is one line until this is opened;
|
|
48
|
+
// the purpose, the policy link and the service's cookie table live behind
|
|
49
|
+
// it. NOT `more`: that string is the banner's «Подробнее» and reads
|
|
50
|
+
// «Aflați mai multe» in ro, which is a sentence, not a row control.
|
|
51
|
+
svcDetails: 'Details',
|
|
52
|
+
// The group's own disclosure: how many services are hidden behind the
|
|
53
|
+
// «N services · M cookies» button. Only ever read by a screen reader.
|
|
54
|
+
svcListLabel: 'Services and cookies in this group',
|
|
47
55
|
// SPEC V1.10 §2 — the blocked-embed placeholder. {host} is the vendor
|
|
48
56
|
// label when the database knows one and the bare host otherwise; {cat} is
|
|
49
57
|
// the localized category title, taken from cat.<name>.title below, so the
|
|
@@ -97,6 +105,8 @@
|
|
|
97
105
|
ckCount: ['{n} cookie', '{n} cookie', '{n} cookie'],
|
|
98
106
|
svcPolicy: 'Политика',
|
|
99
107
|
svcCookies: 'Какие cookie ставит',
|
|
108
|
+
svcDetails: 'Подробнее',
|
|
109
|
+
svcListLabel: 'Сервисы и cookie этой группы',
|
|
100
110
|
phText: 'Здесь содержимое от {host}. Оно загрузится после согласия на «{cat}».',
|
|
101
111
|
phAllow: 'Разрешить и показать',
|
|
102
112
|
phSettings: 'Настроить cookie',
|
|
@@ -142,7 +152,12 @@
|
|
|
142
152
|
// branch in buildStrings() — listing them here would be a bug, because this
|
|
143
153
|
// loop only copies values that are `typeof === 'string'` and would leave
|
|
144
154
|
// every external locale on the English plurals.
|
|
145
|
-
'svcPolicy', 'svcCookies'
|
|
155
|
+
'svcPolicy', 'svcCookies',
|
|
156
|
+
// 0.5.12 — the per-row «Подробнее» disclosure and the accessible name of the
|
|
157
|
+
// group's own one. Both are plain strings, so they belong here: a key left
|
|
158
|
+
// out of this list is `undefined` for all 32 external locales and renders
|
|
159
|
+
// the literal word "undefined" on the card.
|
|
160
|
+
'svcDetails', 'svcListLabel'
|
|
146
161
|
];
|
|
147
162
|
|
|
148
163
|
// Plural-form keys, filled separately from STR_KEYS (see above).
|
|
@@ -420,15 +435,11 @@
|
|
|
420
435
|
'border-color:var(--ck-reject-bd);border-width:var(--ck-reject-bw)}',
|
|
421
436
|
'.ck-btn--settings{background:var(--ck-settings-bg);color:var(--ck-settings-fg);',
|
|
422
437
|
'border-color:var(--ck-settings-bd);border-width:var(--ck-settings-bw)}',
|
|
423
|
-
/*
|
|
438
|
+
/* Unused by the UI since 0.5.11: the preferences panel's footer moved to
|
|
439
|
+
the accept/settings role classes above, so it follows the banner. Kept
|
|
440
|
+
as a compatibility shim for integrator CSS and older inline blocks that
|
|
441
|
+
still name these classes — nothing in this file emits them any more. */
|
|
424
442
|
'.ck-btn--filled{background:var(--ck-accent);border-color:var(--ck-accent);color:var(--ck-on-accent)}',
|
|
425
|
-
/* Live, not a fallback: these paint the preferences panel's own Accept
|
|
426
|
-
all / Reject all. Transparent fill means the LABEL sits on the card, so
|
|
427
|
-
it takes --ck-link like every other accent-coloured text; the BORDER is
|
|
428
|
-
non-text and answers to 3:1, so it keeps the raw accent. The footer's
|
|
429
|
-
background is --ck-soft rather than --ck-bg, but soft is a 5% ink mix of
|
|
430
|
-
the card — the ratio difference is well inside the noise, and a second
|
|
431
|
-
link token per background would break «один код — одни числа». */
|
|
432
443
|
'.ck-btn--outline{background:transparent;border-color:var(--ck-accent);color:var(--ck-link)}',
|
|
433
444
|
'.ck-btn--ghost{min-width:0;border-color:var(--ck-line);color:var(--ck-ink);font-weight:500}',
|
|
434
445
|
|
|
@@ -465,21 +476,50 @@
|
|
|
465
476
|
/* SPEC V1.12 §3 — «N сервисов · M cookie». --ck-muted, like every other
|
|
466
477
|
secondary label on the card, and it is measured for AA against the card
|
|
467
478
|
background by the same rule the description above answers to. */
|
|
479
|
+
/* 0.5.12 — the counter is now the group's disclosure BUTTON. It keeps the
|
|
480
|
+
muted label's size and colour (it is still a secondary label, measured
|
|
481
|
+
for AA against the card background by the same rule as the description),
|
|
482
|
+
and gains a caret and a hit area. `border:0;background:transparent` is
|
|
483
|
+
needed because the panel's reset styles buttons, not because a link is
|
|
484
|
+
being faked — it is a real button and the global :focus-visible rule
|
|
485
|
+
gives it a visible ring. */
|
|
468
486
|
'.ck-cat__count{font-size:12px;font-weight:500;color:var(--ck-muted)}',
|
|
487
|
+
'.ck-cat__toggle{border:0;background:transparent;padding:2px 0;cursor:pointer;',
|
|
488
|
+
'display:inline-flex;align-items:center;gap:5px;font-family:inherit;text-align:left}',
|
|
489
|
+
'.ck-cat__caret{width:0;height:0;flex:none;border:4px solid transparent;',
|
|
490
|
+
'border-left-color:currentColor;border-right:0}',
|
|
491
|
+
'.ck-cat__toggle[aria-expanded="true"] .ck-cat__caret{transform:rotate(90deg)}',
|
|
469
492
|
|
|
470
|
-
/* ---- services inside a group (SPEC V1.12 §3) ---- */
|
|
493
|
+
/* ---- services inside a group (SPEC V1.12 §3, compacted in 0.5.12) ---- */
|
|
471
494
|
/* Indented and rule-separated so the nesting reads without colour: a
|
|
472
495
|
service belongs to the group above it, and its own cookie table belongs
|
|
473
496
|
to it. The left border is the only decoration; everything else is
|
|
474
497
|
spacing, which survives forced-colours mode intact. */
|
|
475
|
-
'.ck-
|
|
476
|
-
'.ck-
|
|
498
|
+
'.ck-cat__region{margin-top:12px}',
|
|
499
|
+
'.ck-svcs{padding-left:12px;border-left:2px solid var(--ck-line)}',
|
|
500
|
+
'.ck-svc{padding:8px 0;border-bottom:1px solid var(--ck-line)}',
|
|
477
501
|
'.ck-svc:first-child{padding-top:2px}',
|
|
478
502
|
'.ck-svc:last-child{border-bottom:0;padding-bottom:2px}',
|
|
479
|
-
|
|
503
|
+
/* One line: name, vendor, then the switch pinned to the right. `center`
|
|
504
|
+
rather than flex-start, because the row is a single line of text now and
|
|
505
|
+
the switch reads as belonging to it. */
|
|
506
|
+
'.ck-svc__top{display:flex;gap:10px;align-items:center}',
|
|
480
507
|
'.ck-svc__txt{flex:1 1 auto;min-width:0}',
|
|
481
|
-
|
|
482
|
-
|
|
508
|
+
/* The name and the vendor share a line and WRAP rather than truncate: at
|
|
509
|
+
320px a long «Google Analytics · Google Ireland Limited» becomes two
|
|
510
|
+
lines and the switch stays where it is, still reachable. */
|
|
511
|
+
'.ck-svc__name{display:flex;align-items:baseline;gap:6px;flex-wrap:wrap;',
|
|
512
|
+
'font-size:14px;font-weight:600;line-height:1.35}',
|
|
513
|
+
'.ck-svc__vendor{margin:0;font-size:12.5px;font-weight:400;color:var(--ck-muted)}',
|
|
514
|
+
/* «всегда активны» on a necessary service's row, where its switch used to
|
|
515
|
+
be: the same pill the group header wears, one step smaller. */
|
|
516
|
+
'.ck-svc__badge{font-size:11px;font-weight:500;color:var(--ck-muted);',
|
|
517
|
+
'border:1px solid var(--ck-line);border-radius:999px;padding:0 7px}',
|
|
518
|
+
/* The row's own «Подробнее». Tighter than a group's <details> — it sits
|
|
519
|
+
inside a row, not between two of them. */
|
|
520
|
+
'.ck-svc__det{margin-top:2px}',
|
|
521
|
+
'.ck-svc__det>summary{font-size:12.5px}',
|
|
522
|
+
'.ck-svc__body{padding:2px 0 4px}',
|
|
483
523
|
'.ck-svc__desc{margin:4px 0 0;font-size:13px;color:var(--ck-muted)}',
|
|
484
524
|
/* --ck-link, not --ck-accent: accent-coloured TEXT goes through the same
|
|
485
525
|
>= 4.5:1 rule as «Подробнее» — see the note on the `a{}` rule above. */
|
|
@@ -516,8 +556,12 @@
|
|
|
516
556
|
'.ck-empty{margin:8px 0 0;font-size:13px;color:var(--ck-muted)}',
|
|
517
557
|
|
|
518
558
|
/* ---- floating button ---- */
|
|
559
|
+
/* 0.5.11 — the floating button wears the ACCEPT button's colours, so the
|
|
560
|
+
one control that outlives the banner still reads as the owner's brand.
|
|
561
|
+
Both tokens are resolved once in resolveButtonStyles (`fab`) and only
|
|
562
|
+
read here — «один код — одни числа». */
|
|
519
563
|
'.ck-fab{position:fixed;left:16px;bottom:16px;z-index:2147482999;width:48px;height:48px;',
|
|
520
|
-
'border-radius:50%;border:1px solid var(--ck-line);background:var(--ck-bg);color:var(--ck-
|
|
564
|
+
'border-radius:50%;border:1px solid var(--ck-line);background:var(--ck-fab-bg);color:var(--ck-fab-fg);',
|
|
521
565
|
'display:inline-flex;align-items:center;justify-content:center;padding:0}',
|
|
522
566
|
'.ck-fab svg{width:24px;height:24px;display:block}',
|
|
523
567
|
|
|
@@ -898,7 +942,7 @@
|
|
|
898
942
|
|
|
899
943
|
/* Config values are interpolated into the TEXT of a generated stylesheet, so
|
|
900
944
|
an unvalidated value can close the declaration and open rules of its own
|
|
901
|
-
("10px;}.ck-btn--
|
|
945
|
+
("10px;}.ck-btn--reject{display:none" hides "Reject all"). config.theme is
|
|
902
946
|
not trusted input: in standalone mode it comes straight from the embedding
|
|
903
947
|
page or an integrator's admin panel, with no server-side validation
|
|
904
948
|
anywhere in the path. So every value is matched against a strict grammar
|
|
@@ -1271,9 +1315,10 @@
|
|
|
1271
1315
|
};
|
|
1272
1316
|
|
|
1273
1317
|
/* The accent as TEXT on the card. Same >= 4.5 rule, same function, one
|
|
1274
|
-
number: links,
|
|
1275
|
-
|
|
1276
|
-
|
|
1318
|
+
number: links, «Подробнее» and the cookie-table summary all read the
|
|
1319
|
+
token this produces, so none of them can drift from what the debug
|
|
1320
|
+
panel and the cabinet quote. (The floating button left this token in
|
|
1321
|
+
0.5.11 — it now follows the accept button; see `fab` below.) Note the
|
|
1277
1322
|
measurement is against cardBg — an accent that is fine inside a filled
|
|
1278
1323
|
button can still be unreadable on the card behind it.
|
|
1279
1324
|
|
|
@@ -1414,6 +1459,46 @@
|
|
|
1414
1459
|
}
|
|
1415
1460
|
out.buttons[role] = rec;
|
|
1416
1461
|
}
|
|
1462
|
+
|
|
1463
|
+
/* The floating button (0.5.11). It is a filled circle that outlives the
|
|
1464
|
+
banner, and the owner's rule is that it wears the ACCEPT button's
|
|
1465
|
+
colours — no setting of its own.
|
|
1466
|
+
|
|
1467
|
+
accept FILLED: take its bg/fg verbatim. That inherits the 0.5.10 rule
|
|
1468
|
+
for free — a typed low-contrast fg stays exactly as typed here too, and
|
|
1469
|
+
is reported through `low` rather than corrected.
|
|
1470
|
+
|
|
1471
|
+
accept OUTLINE: there is no fill to borrow, so the button's BORDER
|
|
1472
|
+
colour becomes the circle and the CARD colour becomes the icon — the
|
|
1473
|
+
outline button's two real colours, swapped. Both are derived for the
|
|
1474
|
+
fab (nobody typed "the floating button is this colour"), so the icon
|
|
1475
|
+
goes through the >= 4.5 rule; ensureContrast picks the readable member
|
|
1476
|
+
of the pair, which on a dark border is the card itself and on a pale
|
|
1477
|
+
one steps away from it. When accept's border is itself derived it is
|
|
1478
|
+
already the accent stepped to 3:1, so this is also the accent/on-accent
|
|
1479
|
+
fallback the brief describes. */
|
|
1480
|
+
var acc = out.buttons.accept;
|
|
1481
|
+
if (acc.variant === 'filled') {
|
|
1482
|
+
out.fab = {
|
|
1483
|
+
bg: acc.bg,
|
|
1484
|
+
fg: acc.fg,
|
|
1485
|
+
ratio: acc.ratio,
|
|
1486
|
+
adjusted: acc.adjusted,
|
|
1487
|
+
low: acc.low,
|
|
1488
|
+
against: acc.bg
|
|
1489
|
+
};
|
|
1490
|
+
} else {
|
|
1491
|
+
var fabBg = acc.border;
|
|
1492
|
+
var fabRes = ensureContrast(cardBg, fabBg, 4.5);
|
|
1493
|
+
out.fab = {
|
|
1494
|
+
bg: fabBg,
|
|
1495
|
+
fg: fabRes.color,
|
|
1496
|
+
ratio: fabRes.ratio,
|
|
1497
|
+
adjusted: fabRes.adjusted,
|
|
1498
|
+
low: below(fabRes.ratio, 4.5),
|
|
1499
|
+
against: fabBg
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1417
1502
|
return out;
|
|
1418
1503
|
}
|
|
1419
1504
|
|
|
@@ -1438,6 +1523,12 @@
|
|
|
1438
1523
|
// same way as __buttons: one pass per mode, so the light and dark blocks
|
|
1439
1524
|
// each carry their own contrast-checked link colour.
|
|
1440
1525
|
if (p.__link) d.push('--ck-link:' + p.__link.color);
|
|
1526
|
+
// The floating button's pair, resolved from the accept button in the same
|
|
1527
|
+
// pass, so light and dark each carry their own (0.5.11).
|
|
1528
|
+
if (p.__fab) {
|
|
1529
|
+
d.push('--ck-fab-bg:' + p.__fab.bg);
|
|
1530
|
+
d.push('--ck-fab-fg:' + p.__fab.fg);
|
|
1531
|
+
}
|
|
1441
1532
|
var btns = p.__buttons;
|
|
1442
1533
|
if (btns) {
|
|
1443
1534
|
for (var i = 0; i < BTN_ROLES.length; i++) {
|
|
@@ -1511,17 +1602,20 @@
|
|
|
1511
1602
|
light.__buttons = lightBtn.buttons;
|
|
1512
1603
|
dark.__buttons = darkBtn.buttons;
|
|
1513
1604
|
|
|
1514
|
-
// `--ck-on-accent` paints the text of
|
|
1515
|
-
// banner
|
|
1516
|
-
//
|
|
1517
|
-
//
|
|
1518
|
-
//
|
|
1605
|
+
// `--ck-on-accent` paints the text of the remaining filled surfaces that
|
|
1606
|
+
// are not banner buttons — since 0.5.11 that is the blocked-embed
|
|
1607
|
+
// placeholder's button, the panel foot and the floating button having
|
|
1608
|
+
// moved onto the accept button's own tokens. A derived onAccent still has
|
|
1609
|
+
// to pass the >= 4.5 rule against the accent, or a white accent gets white
|
|
1610
|
+
// text (the owner saw exactly that). Done AFTER the buttons are resolved, so
|
|
1519
1611
|
// the accept button still records `adjusted:true` for the same correction.
|
|
1520
1612
|
// An onAccent the owner set is painted as set — the 0.5.10 rule.
|
|
1521
1613
|
if (!light.onAccentExplicit) light.onAccent = ensureContrast(light.onAccent, light.accent, 4.5).color;
|
|
1522
1614
|
if (!dark.onAccentExplicit) dark.onAccent = ensureContrast(dark.onAccent, dark.accent, 4.5).color;
|
|
1523
1615
|
light.__link = lightBtn.link;
|
|
1524
1616
|
dark.__link = darkBtn.link;
|
|
1617
|
+
light.__fab = lightBtn.fab;
|
|
1618
|
+
dark.__fab = darkBtn.fab;
|
|
1525
1619
|
|
|
1526
1620
|
var mode = theme.mode;
|
|
1527
1621
|
if (mode !== 'light' && mode !== 'dark') mode = 'auto';
|
|
@@ -1658,7 +1752,23 @@
|
|
|
1658
1752
|
}
|
|
1659
1753
|
}
|
|
1660
1754
|
|
|
1755
|
+
/* SPEC V1.12 §3, reshaped in 0.5.12 — one service, on ONE line.
|
|
1756
|
+
|
|
1757
|
+
0.5.8 rendered the name, the vendor, the purpose paragraph, the policy link
|
|
1758
|
+
and a cookie table for every service, all at once. Eight services filled the
|
|
1759
|
+
panel and the visitor scrolled past the switches they came for. The row is
|
|
1760
|
+
now: name (bold) · vendor (muted) · switch, and nothing else until the row's
|
|
1761
|
+
own «Подробнее» is opened — behind which the purpose, the policy link and
|
|
1762
|
+
«Какие cookie ставит (N)» live, unchanged.
|
|
1763
|
+
|
|
1764
|
+
`cat === 'necessary'` renders NO switch. The group is always on and cannot
|
|
1765
|
+
be refused, so a control that could only ever sit at «off» (which is what
|
|
1766
|
+
0.5.11 showed, because syncGroup() only ever ran for the opt-in groups)
|
|
1767
|
+
tells the visitor a lie about what they can change. The «всегда активны»
|
|
1768
|
+
badge the group header already carries says the true thing instead. */
|
|
1661
1769
|
function buildService(svc, cat, rows) {
|
|
1770
|
+
var locked = cat === 'necessary';
|
|
1771
|
+
|
|
1662
1772
|
var wrap = el('div', 'ck-svc');
|
|
1663
1773
|
var top = el('div', 'ck-svc__top');
|
|
1664
1774
|
var txt = el('div', 'ck-svc__txt');
|
|
@@ -1668,45 +1778,68 @@
|
|
|
1668
1778
|
var nameSpan = el('span', null, svc.name);
|
|
1669
1779
|
nameSpan.id = nameId;
|
|
1670
1780
|
name.appendChild(nameSpan);
|
|
1781
|
+
// Vendor on the SAME line as the name now, not a paragraph under it. The
|
|
1782
|
+
// row wraps rather than truncates, so a long pair still reads at 320px.
|
|
1783
|
+
if (svc.vendor) name.appendChild(el('span', 'ck-svc__vendor', svc.vendor));
|
|
1784
|
+
if (locked) name.appendChild(el('span', 'ck-svc__badge', T.alwaysOn));
|
|
1671
1785
|
txt.appendChild(name);
|
|
1672
1786
|
|
|
1673
|
-
|
|
1787
|
+
top.appendChild(txt);
|
|
1674
1788
|
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
// The link text is the same word on every row, so a screen reader needs
|
|
1693
|
-
// the service name to tell them apart.
|
|
1694
|
-
a.setAttribute('aria-label', T.svcPolicy + ' — ' + svc.name);
|
|
1695
|
-
txt.appendChild(a);
|
|
1696
|
-
}
|
|
1697
|
-
|
|
1698
|
-
var sw = makeServiceSwitch(svc, cat);
|
|
1699
|
-
sw.setAttribute('aria-labelledby', nameId);
|
|
1700
|
-
if (descId) sw.setAttribute('aria-describedby', descId);
|
|
1701
|
-
if (!serviceSwitches[cat]) serviceSwitches[cat] = [];
|
|
1702
|
-
serviceSwitches[cat].push(sw);
|
|
1789
|
+
/* No switch for a necessary service — and no entry in serviceSwitches[],
|
|
1790
|
+
which is what keeps readSwitches() from ever writing such an id into the
|
|
1791
|
+
denial map. (It iterates OPT_IN, so it could not today; not registering
|
|
1792
|
+
the switch means it still cannot if that loop ever widens.) */
|
|
1793
|
+
if (!locked) {
|
|
1794
|
+
var sw = makeServiceSwitch(svc, cat);
|
|
1795
|
+
/* aria-labelledby only. 0.5.8 also pointed aria-describedby at the purpose
|
|
1796
|
+
paragraph; in 0.5.12 that paragraph lives inside a CLOSED <details>, and
|
|
1797
|
+
a description a screen reader cannot reach until the visitor opens
|
|
1798
|
+
something else is worse than none — the name and the switch's own state
|
|
1799
|
+
are what the control has to announce. The purpose is still there, one
|
|
1800
|
+
«Подробнее» away, as its own readable text. */
|
|
1801
|
+
sw.setAttribute('aria-labelledby', nameId);
|
|
1802
|
+
if (!serviceSwitches[cat]) serviceSwitches[cat] = [];
|
|
1803
|
+
serviceSwitches[cat].push(sw);
|
|
1804
|
+
top.appendChild(sw);
|
|
1805
|
+
}
|
|
1703
1806
|
|
|
1704
|
-
top.appendChild(txt);
|
|
1705
|
-
top.appendChild(sw);
|
|
1706
1807
|
wrap.appendChild(top);
|
|
1707
1808
|
|
|
1809
|
+
/* Everything else goes behind the row's own disclosure. <details>/<summary>
|
|
1810
|
+
rather than a button: the keyboard behaviour, the focus ring and the
|
|
1811
|
+
expanded state come from the browser, and it is the same control the
|
|
1812
|
+
cookie tables have used since 0.5.7. */
|
|
1813
|
+
var purpose = servicePurpose(svc);
|
|
1708
1814
|
var own = cookieRowsForService(rows, svc);
|
|
1709
|
-
if (
|
|
1815
|
+
if (purpose || svc.privacyUrl || own.length) {
|
|
1816
|
+
var det = el('details', 'ck-det ck-svc__det');
|
|
1817
|
+
var sum = el('summary');
|
|
1818
|
+
sum.appendChild(document.createTextNode(T.svcDetails));
|
|
1819
|
+
// The word is the same on every row, so a screen reader needs the service
|
|
1820
|
+
// name to tell one «Подробнее» from the next.
|
|
1821
|
+
sum.setAttribute('aria-label', T.svcDetails + ' — ' + svc.name);
|
|
1822
|
+
det.appendChild(sum);
|
|
1823
|
+
|
|
1824
|
+
var body = el('div', 'ck-svc__body');
|
|
1825
|
+
if (purpose) body.appendChild(el('p', 'ck-svc__desc', purpose));
|
|
1826
|
+
|
|
1827
|
+
/* «Политика» — target=_blank rel=noopener, per §3. The URL is already
|
|
1828
|
+
http(s)-validated by the core's normalizeService(), which is where a
|
|
1829
|
+
javascript: address is dropped; nothing unvalidated reaches an href. */
|
|
1830
|
+
if (svc.privacyUrl) {
|
|
1831
|
+
var a = el('a', 'ck-svc__policy', T.svcPolicy);
|
|
1832
|
+
a.href = svc.privacyUrl;
|
|
1833
|
+
a.target = '_blank';
|
|
1834
|
+
a.rel = 'noopener noreferrer';
|
|
1835
|
+
a.setAttribute('aria-label', T.svcPolicy + ' — ' + svc.name);
|
|
1836
|
+
body.appendChild(a);
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
if (own.length) body.appendChild(cookieTable(own, T.svcCookies + ' (' + own.length + ')'));
|
|
1840
|
+
det.appendChild(body);
|
|
1841
|
+
wrap.appendChild(det);
|
|
1842
|
+
}
|
|
1710
1843
|
|
|
1711
1844
|
return wrap;
|
|
1712
1845
|
}
|
|
@@ -1748,6 +1881,27 @@
|
|
|
1748
1881
|
return det;
|
|
1749
1882
|
}
|
|
1750
1883
|
|
|
1884
|
+
/* 0.5.12 — the group's disclosure button.
|
|
1885
|
+
|
|
1886
|
+
«N сервисов · M cookie» was a static label in 0.5.8; it is now the control
|
|
1887
|
+
that reveals the group's services and its cookie list, COLLAPSED by default.
|
|
1888
|
+
A real <button aria-expanded> rather than a <details>, because the panel
|
|
1889
|
+
already owns the region it toggles — the services list and the loose-cookie
|
|
1890
|
+
table are siblings that must move together, and wrapping both in a
|
|
1891
|
+
<details> would put a second summary line above the one the header already
|
|
1892
|
+
has. Keyboard and focus come free with a button; the ring is the panel's
|
|
1893
|
+
own :focus-visible rule. */
|
|
1894
|
+
function makeGroupToggle(cat, label, regionId) {
|
|
1895
|
+
var b = el('button', 'ck-cat__count ck-cat__toggle');
|
|
1896
|
+
b.type = 'button';
|
|
1897
|
+
b.setAttribute('aria-expanded', 'false');
|
|
1898
|
+
b.setAttribute('aria-controls', regionId);
|
|
1899
|
+
b.appendChild(el('span', 'ck-cat__caret'));
|
|
1900
|
+
b.appendChild(document.createTextNode(label));
|
|
1901
|
+
b.dataset.cat = cat;
|
|
1902
|
+
return b;
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1751
1905
|
function buildCategory(cfg, cat) {
|
|
1752
1906
|
var meta = T.cat[cat] || { title: cat, desc: '' };
|
|
1753
1907
|
var locked = cat === 'necessary';
|
|
@@ -1779,9 +1933,11 @@
|
|
|
1779
1933
|
«0 сервисов · 3 cookie». The cookie half counts the whole group, services
|
|
1780
1934
|
and loose rows alike: it answers «сколько cookie в этой группе», which is
|
|
1781
1935
|
the question the line is there to answer. */
|
|
1936
|
+
var toggle = null;
|
|
1937
|
+
var regionId = nameId + '-svcs';
|
|
1782
1938
|
if (svcs.length) {
|
|
1783
|
-
|
|
1784
|
-
|
|
1939
|
+
toggle = makeGroupToggle(cat, groupCountLabel(svcs.length, rows.length, T, LANG), regionId);
|
|
1940
|
+
name.appendChild(toggle);
|
|
1785
1941
|
}
|
|
1786
1942
|
|
|
1787
1943
|
var sw = makeSwitch(cat, locked);
|
|
@@ -1793,23 +1949,47 @@
|
|
|
1793
1949
|
top.appendChild(sw);
|
|
1794
1950
|
wrap.appendChild(top);
|
|
1795
1951
|
|
|
1952
|
+
// «Cookie в этой группе (N)» keeps its old meaning: what is left once each
|
|
1953
|
+
// service has claimed its own. With no services that is the whole table and
|
|
1954
|
+
// the summary line is byte-for-byte what 0.5.7 rendered.
|
|
1955
|
+
var loose = looseCookies(rows, svcs);
|
|
1956
|
+
|
|
1796
1957
|
if (svcs.length) {
|
|
1958
|
+
/* Both halves live inside the collapsed region: the services and, at the
|
|
1959
|
+
end, the group's own «Какие cookie (N)». `hidden` rather than a class,
|
|
1960
|
+
so the region is out of the accessibility tree and out of the tab order
|
|
1961
|
+
while collapsed — a class that only sets display:none would leave the
|
|
1962
|
+
switches focusable to a keyboard user who cannot see them. */
|
|
1963
|
+
var region = el('div', 'ck-cat__region');
|
|
1964
|
+
region.id = regionId;
|
|
1965
|
+
region.hidden = true;
|
|
1966
|
+
|
|
1797
1967
|
var list = el('div', 'ck-svcs');
|
|
1798
1968
|
// A list, so a screen reader announces «3 items» before reading them.
|
|
1799
1969
|
list.setAttribute('role', 'list');
|
|
1970
|
+
list.setAttribute('aria-label', T.svcListLabel);
|
|
1800
1971
|
for (var s = 0; s < svcs.length; s++) {
|
|
1801
1972
|
var item = buildService(svcs[s], cat, rows);
|
|
1802
1973
|
item.setAttribute('role', 'listitem');
|
|
1803
1974
|
list.appendChild(item);
|
|
1804
1975
|
}
|
|
1805
|
-
|
|
1806
|
-
}
|
|
1976
|
+
region.appendChild(list);
|
|
1807
1977
|
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1978
|
+
if (loose.length) {
|
|
1979
|
+
region.appendChild(cookieTable(loose, T.cookiesIn + ' (' + loose.length + ')'));
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
toggle.addEventListener('click', function () {
|
|
1983
|
+
var open = toggle.getAttribute('aria-expanded') === 'true';
|
|
1984
|
+
toggle.setAttribute('aria-expanded', open ? 'false' : 'true');
|
|
1985
|
+
region.hidden = open;
|
|
1986
|
+
});
|
|
1987
|
+
|
|
1988
|
+
wrap.appendChild(region);
|
|
1989
|
+
} else if (loose.length) {
|
|
1990
|
+
// No services: the cookie table sits where it always did, top level and
|
|
1991
|
+
// never behind a disclosure — a pre-0.5.8 config renders as it did in
|
|
1992
|
+
// 0.5.7, which is what «старый конфиг рендерится как раньше» asks for.
|
|
1813
1993
|
wrap.appendChild(cookieTable(loose, T.cookiesIn + ' (' + loose.length + ')'));
|
|
1814
1994
|
}
|
|
1815
1995
|
|
|
@@ -1997,11 +2177,21 @@
|
|
|
1997
2177
|
p.appendChild(body);
|
|
1998
2178
|
|
|
1999
2179
|
var foot = el('div', 'ck-panel__foot');
|
|
2000
|
-
|
|
2180
|
+
/* 0.5.11 — the panel follows the BANNER's buttons, with no setting of its
|
|
2181
|
+
own: «Сохранить выбор» is the same role as «Принять всё» and wears the
|
|
2182
|
+
accept class, «Принять всё»/«Отклонить всё» here are secondary and wear
|
|
2183
|
+
the settings («Настроить») class. Reusing the role classes means the
|
|
2184
|
+
filled/outline split stays a VALUE change in the tokens — the panel
|
|
2185
|
+
never picks a class from the resolved variant, so a theme.buttons edit
|
|
2186
|
+
is still applyTheme()-only and signature() still need not see buttons.
|
|
2187
|
+
Panel layout is untouched: `.ck-panel__foot .ck-btn{flex:1 1 150px}`
|
|
2188
|
+
(0,2,0) outranks `.ck-btn{flex:1 1 auto}` and the role classes set no
|
|
2189
|
+
flex of their own. */
|
|
2190
|
+
var save = el('button', 'ck-btn ck-btn--accept', T.save);
|
|
2001
2191
|
save.type = 'button';
|
|
2002
|
-
var acc = el('button', 'ck-btn ck-btn--
|
|
2192
|
+
var acc = el('button', 'ck-btn ck-btn--settings', T.acceptAll);
|
|
2003
2193
|
acc.type = 'button';
|
|
2004
|
-
var rej = el('button', 'ck-btn ck-btn--
|
|
2194
|
+
var rej = el('button', 'ck-btn ck-btn--settings', T.rejectAll);
|
|
2005
2195
|
rej.type = 'button';
|
|
2006
2196
|
|
|
2007
2197
|
save.addEventListener('click', function () { doSave(); });
|