@owlmeans/web-consent 0.1.18-rc.3 → 0.1.18-rc.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +15 -0
  2. package/agent-meta/manifest.json +16 -0
  3. package/agent-meta/skills/web-consent/SKILL.md +277 -0
  4. package/build/consent/component.d.ts +0 -8
  5. package/build/consent/component.d.ts.map +1 -1
  6. package/build/consent/component.js +17 -2
  7. package/build/consent/component.js.map +1 -1
  8. package/build/consent/toggle.d.ts +15 -0
  9. package/build/consent/toggle.d.ts.map +1 -1
  10. package/build/consent/toggle.js +22 -1
  11. package/build/consent/toggle.js.map +1 -1
  12. package/build/consent/widget.d.ts +18 -0
  13. package/build/consent/widget.d.ts.map +1 -0
  14. package/build/consent/widget.js +31 -0
  15. package/build/consent/widget.js.map +1 -0
  16. package/build/index.d.ts +2 -1
  17. package/build/index.d.ts.map +1 -1
  18. package/build/index.js +1 -0
  19. package/build/index.js.map +1 -1
  20. package/build/policy/component.d.ts +6 -0
  21. package/build/policy/component.d.ts.map +1 -1
  22. package/build/policy/component.js +23 -1
  23. package/build/policy/component.js.map +1 -1
  24. package/build/types.d.ts +17 -1
  25. package/build/types.d.ts.map +1 -1
  26. package/package.json +10 -3
  27. package/src/consent/component.tsx +44 -16
  28. package/src/consent/toggle.tsx +59 -29
  29. package/src/consent/widget.tsx +43 -0
  30. package/src/index.ts +3 -2
  31. package/src/policy/component.tsx +80 -6
  32. package/src/types.ts +18 -1
  33. package/tests/a11y.spec.ts +117 -0
  34. package/tests/bundle.spec.ts +36 -0
  35. package/tests/consent.spec.ts +398 -0
  36. package/tests/context.ts +51 -0
  37. package/tests/harness/index.html +22 -0
  38. package/tests/harness/mount.tsx +101 -0
  39. package/tests/harness/styled.css +54 -0
  40. package/build/.gitkeep +0 -0
  41. package/build/@/lib/utils.d.ts +0 -3
  42. package/build/@/lib/utils.d.ts.map +0 -1
  43. package/build/@/lib/utils.js +0 -6
  44. package/build/@/lib/utils.js.map +0 -1
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ <!-- owlmeans:agent-guidance:start -->
2
+ ## Agent guidance
3
+
4
+ This package ships embedded agent skills under `agent-meta/`. After installing your
5
+ `@owlmeans/*` packages, run the OwlMeans agent-skills installer to place them into
6
+ your project's skill store (`.agents/skills/`):
7
+
8
+ ```sh
9
+ npx @owlmeans/agent-skills@^0.1.18-rc.36
10
+ ```
11
+
12
+ The embedded files are version-matched to this package release. Do not edit them
13
+ directly — they are regenerated on each publish. To contribute guidance edits,
14
+ open a PR against the source monorepo.
15
+ <!-- owlmeans:agent-guidance:end -->
@@ -0,0 +1,16 @@
1
+ {
2
+ "schemaVersion": 2,
3
+ "package": "@owlmeans/web-consent",
4
+ "version": "0.1.18-rc.30",
5
+ "generatedAt": "2026-09-22T22:24:39.577Z",
6
+ "canonicalRepo": "https://github.com/owlmeans/common",
7
+ "entries": [
8
+ {
9
+ "kind": "skill",
10
+ "name": "web-consent",
11
+ "category": "package-specific",
12
+ "file": "skills/web-consent/SKILL.md",
13
+ "canonicalPath": ".agents/skills/web-consent/SKILL.md"
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1,277 @@
1
+ ---
2
+ name: web-consent
3
+ description: How to use @owlmeans/web-consent — the React cookie-consent dialog, its re-open button, the generated cookie-policy page and the useConsent hooks, plus the Tailwind @source line every consumer must add. Auto-invoked when mounting a consent dialog, rendering a cookie policy, gating a feature on a consent category, or importing CookieConsent, CookiePolicy or useConsent.
4
+ user-invocable: false
5
+ ---
6
+ <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
7
+
8
+ # @owlmeans/web-consent
9
+
10
+ **Layer:** Web (React)
11
+ **Install:** `"@owlmeans/web-consent": "^0.1.18-rc.30"` in `dependencies`
12
+
13
+ The browser components of the consent set. The model — categories, storage, migration, the store,
14
+ Consent Mode signalling — is `@owlmeans/consent`, and this package re-exports a **named selection**
15
+ of it (listed under Key Exports) so an application usually has one import. Five public
16
+ `@owlmeans/consent` exports are deliberately not in that list — `makeConsentStore`,
17
+ `normalizeLocale`, `CONSENT_SETUP_FLAG`, `CONSENT_SIGNAL_DEFAULTS` and the `ConsentListener` type —
18
+ so a caller that needs one of them imports it from `@owlmeans/consent` directly. **Read the
19
+ `consent` skill for the model; read this one for the components.**
20
+
21
+ ## Deliberately not a shadcn package
22
+
23
+ It uses **no shadcn primitive and no `@` alias**, and owns a four-line `cn` of its own. The `@`
24
+ contract exists so a consumer's THEME and primitives win — `cn` has neither in it, and requiring an
25
+ alias for it would mean adopting an OwlMeans UI contract before you may render a consent notice.
26
+ One of the surfaces that needs this dialog most is a static site with its own component library and
27
+ no such alias, where that would simply fail to build.
28
+
29
+ For the same reason it is not part of `@owlmeans/web-panel`: that would drag ~30 packages onto a
30
+ cookie notice. `@owlmeans/web-panel/consent` is the thin binding to OwlMeans i18n on top of these
31
+ components — see below.
32
+
33
+ ## Key Exports
34
+
35
+ | Export | Description |
36
+ |--------|-------------|
37
+ | `CookieConsent` | The preferences dialog **and** the persistent re-open button |
38
+ | `ConsentMenuWidget` | A plain row a HOST'S OWN menu renders to reopen the dialog, in place of (or beside) `CookieConsent`'s own floating button — see below |
39
+ | `CookiePolicy` | The cookie-policy page, generated from the configuration in force |
40
+ | `ConsentToggle` | One category row — locked and labelled when the category is required |
41
+ | `useConsent(opts?)` | This document's consent state and the actions over it (`UseConsentModel`) — **it also initialises the store on mount**, see below |
42
+ | `useConsentCategory(key)` | Whether one category is granted, for a component gating a single thing |
43
+ | `CookieConsentProps` / `CookiePolicyProps` / `ConsentLink` | The component props |
44
+ | Re-exports from `@owlmeans/consent` | The complete list: `consentStore`, `openConsent`, `isConsented`, `readConsent`, `writeConsent`, `clearConsent`, `migrateConsent`, `applyConsent`, `pushConsentDefaults`, `consentBootstrapScript`, `consentDefaults`, `consentUpdate`, `gtagConsent`, `DEFAULT_CONSENT_CATEGORIES`, `DEFAULT_CONSENT_MESSAGES`, `defaultConsentTranslate`, `interpolate`, `CONSENT_KEY`, `CONSENT_COOKIE_DAYS`, `CONSENT_SCHEMA_VERSION`, `CONSENT_LOCALES`, `CONSENT_ESSENTIAL` / `CONSENT_ANALYTICS` / `CONSENT_MARKETING`, and the types `ConsentCategory`, `ConsentOptions`, `ConsentReason`, `ConsentRecord`, `ConsentService`, `ConsentSignal`, `ConsentState`, `ConsentStore`, `ConsentLocale` |
45
+
46
+ ## Mounting the dialog
47
+
48
+ `CookieConsent` is mounted **once**, at the application root or in the layout. It opens itself when
49
+ no decision is stored, renders nothing but the re-open button once one is, and needs no state from
50
+ the caller:
51
+
52
+ ```tsx
53
+ import { CookieConsent } from '@owlmeans/web-consent'
54
+
55
+ <CookieConsent
56
+ policyHref="/legal/cookies"
57
+ links={[{ href: '/legal/privacy', labelKey: 'privacy', defaultLabel: 'Privacy Policy' }]}
58
+ />
59
+ ```
60
+
61
+ - **The category set the dialog renders is the set it saves.** Pass `categories` here and pass the
62
+ same set to whatever stamps the head snippet, or the two disagree about what was asked. The same
63
+ goes for `storageKey`, `cookieDays` and `cookieDomain`.
64
+ - `locale` picks the packaged language; leave it unset and English is used, so an app with a
65
+ language of its own passes it (or mounts `PanelCookieConsent`, which does). A region tag is
66
+ reduced to its base (`pl-PL` → `pl`), and anything outside `CONSENT_LOCALES` falls back to English.
67
+ - `policyHref` is a plain string, so an app-resolved path, a framework route and a raw href all
68
+ work — the component must not know how its host does routing.
69
+ - `noReopenButton` hides the floating button for an app that offers a footer link instead; that link
70
+ calls `openConsent('reopen')`.
71
+ - **The re-open button is a bare icon in the very corner (`bottom-1 left-1`), not a card.** No
72
+ filled background, no border, no shadow, no hover-scale — `bg-transparent`, dimmed
73
+ (`opacity-70`) at rest and picked out on hover/focus — because it sits on every page of a site
74
+ for as long as a visitor stays and must read as a small fixture rather than compete with the
75
+ page's own controls. The pictogram is 20px (`h-5 w-5`) inside an invisible 44px hit area
76
+ (`h-11 w-11`). `[data-consent-reopen]` is what any test or CSS override keys on.
77
+
78
+ ## The look — flat, from the host's tokens
79
+
80
+ The dialog is the first thing every new visitor of a generated app sees, so it follows the flat
81
+ rule those apps are held to: **no gradient, shadow, glow, glass or `backdrop-filter`** anywhere in
82
+ it. Everything is a theme token, so light and dark follow the host:
83
+
84
+ | Part | Classes |
85
+ |---|---|
86
+ | Overlay | `bg-black/70`, flat — no blur |
87
+ | Card | `rounded-3xl border border-border bg-background text-foreground`, no shadow or ring |
88
+ | Icon | `rounded-full bg-muted text-primary` |
89
+ | Category row | `rounded-2xl border border-border`; *Required* is plain uppercase muted text, never a badge |
90
+ | Switch | `bg-primary` on / `bg-muted-foreground/40` off, white knob; 44px hit area around the 24×44 track |
91
+ | Accept | the one accent pill — `rounded-full bg-primary text-primary-foreground min-h-11` |
92
+ | Save, policy *Manage preferences* | outlined pill — `rounded-full border-[1.5px] border-foreground bg-transparent`, `hover:bg-muted` |
93
+ | Links | muted, underlined at rest, `min-h-11` |
94
+
95
+ Every control shows a 3px `outline-ring` focus outline 3px off the element (`outline-3
96
+ outline-offset-3`; the switch draws it on its track through `peer-focus-visible`). An outline, not
97
+ a ring, so a host that zeroes shadows keeps it. Never reintroduce a decorative class here — the
98
+ package tests fail on `bg-gradient`, `backdrop-`, `blur`, `shadow` or `ring-primary` in the dialog.
99
+
100
+ **Each switch is named by reference.** The `<label>` around a switch's checkbox holds only the
101
+ drawn track, and the category's words sit in a sibling column it cannot reach, so the checkbox
102
+ carries `aria-labelledby="<id>-label"` (the category label) and `aria-describedby` pointing at
103
+ `<id>-required` and `<id>-desc`. Drop those and axe reports three unnamed checkboxes (`label`,
104
+ critical). A required category stays `disabled` and keeps its name.
105
+
106
+ `tests/a11y.spec.ts` runs axe-core (a dev dependency) against the open dialog in both schemes, the
107
+ re-open button, and the policy page with services, and fails on any serious or critical
108
+ violation. It loads the harness with `?styled=1` (plus `&theme=dark`), which compiles the package
109
+ sources with a generated app's neutral tokens — contrast cannot be judged on the unstyled harness
110
+ the other specs need.
111
+ - `silent` skips every `dataLayer` and global write. It is for tests and for an app that runs no
112
+ tags at all.
113
+ - The draft is **re-seeded from storage every time the dialog opens**, not from the last render — a
114
+ visitor reopening preferences must see the answer they gave.
115
+
116
+ **When the dialog was raised by something waiting on it** — `reason === 'login'`, which the sign-in
117
+ precondition in `@owlmeans/client-iam` raises — it says so and relabels the primary action *Accept
118
+ & continue*. That is what makes the interruption legible instead of looking like the page asking
119
+ twice. Word that path as an acknowledgement, never as "you must consent to essential cookies": a
120
+ required category is disclosure, not a question.
121
+
122
+ ## A menu row for a host that already has one
123
+
124
+ `ConsentMenuWidget` renders one row — icon, translated label, `onClick` reopening the dialog
125
+ (`openConsent('reopen')` by default, or a caller's own `onSelect`) — for a host whose own
126
+ navigation already carries a settings menu and would rather offer cookie preferences there than as
127
+ a second floating button. It calls `openConsent` directly, never `useConsent()`, for the same
128
+ init-on-mount reason as everywhere else in this document.
129
+
130
+ **It carries no presence signalling of its own.** A host that wants `CookieConsent`'s floating
131
+ button to hide while this row is reachable needs to know that itself, from something that stays
132
+ mounted for as long as the row is reachable — never from the row's own mount. A row placed inside a
133
+ host menu's lazily-rendered content (Radix's `DropdownMenuContent`, and most headless menu
134
+ primitives, only mount their children while the menu is actually OPEN) reports "present" for a
135
+ fraction of the time the menu itself is on screen, and the floating button reappears the instant
136
+ the menu closes. `@owlmeans/web-panel/consent`'s `useConsentMenuPresence()` is the fix for a
137
+ context-aware host: call it from the menu's own always-mounted shell component, not from inside the
138
+ row. A context-free host (an Astro island with its own menu) owns that judgement itself.
139
+
140
+ ## Reading the decision
141
+
142
+ ```tsx
143
+ import { useConsent, useConsentCategory, isConsented } from '@owlmeans/web-consent'
144
+
145
+ const consent = useConsent() // { record, open, reason, granted, save, acceptAll, openDialog, close }
146
+ const analytics = useConsentCategory('analytics') // one category, for a component gating one thing
147
+ if (isConsented('analytics')) { /* outside React — a click handler, a service */ }
148
+ ```
149
+
150
+ Both hooks subscribe through `useSyncExternalStore` over the module-singleton store, because consent
151
+ is a property of the DOCUMENT rather than of a component tree: the dialog, the re-open button, the
152
+ policy page and whatever an app gates on it all read one value, and local copies would disagree the
153
+ moment one of them saved. During server rendering the snapshot is "no record, closed", so the dialog
154
+ never flashes into static HTML before hydration corrects it.
155
+
156
+ **`useConsent` is not a pure reader — it runs `consentStore.init(opts)` on mount.** Two consequences
157
+ a component that only wanted to read has to plan for:
158
+
159
+ - **It opens the dialog.** `init` reads storage, and with no stored record it publishes
160
+ `open: true, reason: 'initial'`. So a `useConsent()` in a card that merely wanted `granted('analytics')`
161
+ gates the page for a first-time visitor. Read a single category with `useConsentCategory(key)` —
162
+ that hook subscribes and does **not** init — or `isConsented(key)` outside React.
163
+ - **The first `useConsent` to mount fixes the Consent Mode defaults.** `init` calls
164
+ `pushConsentDefaults`, which is idempotent through the `CONSENT_SETUP_FLAG` window flag: whoever
165
+ gets there first declares the `consent/default` signals from *its* categories, and every later
166
+ call returns immediately. A bare `useConsent()` mounting before `CookieConsent` therefore declares
167
+ `DEFAULT_CONSENT_CATEGORIES`' signals and the app's own `categories` never declare theirs — which
168
+ silently breaks the parity rule above. Pass the app's `opts` wherever `useConsent` is called, or
169
+ do not call it outside the dialog.
170
+
171
+ `opts` are read on the mounting pass only (the effect's dependency list is empty), so changing them
172
+ in a later render has no effect on that mount.
173
+
174
+ ## The policy page
175
+
176
+ `CookiePolicy` states only what the widget provably does — the categories in force, the storage key,
177
+ the dual storage, the retention — all read from the same configuration the dialog renders. That is
178
+ why it is generated rather than written: a hand-written policy drifts the first time a category
179
+ changes, and nobody notices because nobody reads it until it matters.
180
+
181
+ ```tsx
182
+ import { googleTagServices } from '@owlmeans/web-gtm'
183
+
184
+ <CookiePolicy
185
+ operator="Example Sp. z o.o." privacyHref="/legal/privacy" termsHref="/legal/terms"
186
+ services={googleTag !== '' ? googleTagServices(googleTag) : undefined}
187
+ />
188
+ ```
189
+
190
+ Everything OwlMeans cannot assert on the operator's behalf — who the controller is, the lawful
191
+ basis, how to exercise rights — is deferred to those two links. It also renders a *Manage
192
+ preferences* control, so the policy page is a way back into the decision.
193
+
194
+ **`services`** (`ConsentService[]`, see the `consent` skill) discloses WHO receives data: each
195
+ service is listed inside the item of the category whose `key` it names — name, provider, purpose,
196
+ cookie names in `<code>`, and a link to the provider's privacy policy (new tab). A service whose
197
+ category is not among `categories` goes into a trailing *Other services* item rather than being
198
+ dropped. Without `services` the page renders exactly as it always has.
199
+
200
+ The structure stays one `h1` and lists: categories are `li`s, a category's services are a nested
201
+ `ul` named by `aria-label` (*Services — ‹category label›*), each service a `li` holding a `dl`.
202
+ Never add headings for categories or services — they would skip a level under the `h1`. Test and
203
+ CSS hooks: `[data-cookie-policy-category="<key>"]`, `[data-cookie-policy-services]`,
204
+ `[data-cookie-policy-service]`, `[data-cookie-policy-other]`.
205
+
206
+ The service labels (`policyProvider`, `policyPurpose`, `policyCookies`, `policyServicesOf`,
207
+ `policyServicePrivacy`, `policyOtherServices`, `policyOtherServicesDesc`) go through `translate`
208
+ with English defaults and are in the packaged bundle for every locale; the service's own `name`,
209
+ `provider` and `purpose` are data and render as given.
210
+
211
+ ## Tailwind — one `@source` line, pointing at `src`
212
+
213
+ The components emit Tailwind classes that exist nowhere in the consumer's own sources, and
214
+ Tailwind's scanner reads the CSS root plus `@source` directives only, excluding `node_modules`. Add
215
+ the line to the app's Tailwind entry:
216
+
217
+ ```css
218
+ @import "tailwindcss";
219
+
220
+ @source "<relative path to node_modules>/@owlmeans/web-consent/src";
221
+ ```
222
+
223
+ **Point it at `src`, never at `build`.** The scanner applies the `.gitignore` of whatever repository
224
+ a path resolves into, and under a linked workspace the `node_modules` entry is a symlink into a
225
+ monorepo where every package's build output is ignored. A `build` source there scans **zero files
226
+ and reports nothing**: the build succeeds, the CSS is emitted, and the dialog renders *half*-styled
227
+ — the utilities the app happens to use elsewhere still exist while the ones only this package asks
228
+ for (`max-w-lg`, `bg-black/70`, `z-[999998]`) do not. What reaches the screen is a full-width,
229
+ backdrop-less dialog with the page bleeding through, and nothing in the app's sources looks wrong.
230
+ `src` is tracked and ships in the published tarball, so one path serves both a linked checkout and
231
+ an npm install.
232
+
233
+ The failure is silent, so verify rather than assume — grep the emitted stylesheet for a class only
234
+ this package uses:
235
+
236
+ ```sh
237
+ grep -c 'max-w-lg' dist/assets/*.css
238
+ ```
239
+
240
+ A `*/` inside a CSS comment ends it early, so a comment above an `@source` that spells out a glob
241
+ closes the comment at that star-slash and turns the next `@source` into an invalid declaration.
242
+ Describe a glob in words, or keep it out of the comment.
243
+
244
+ ## i18n
245
+
246
+ Given no `translate` prop, the components resolve every string through the packaged bundle for
247
+ `locale` — every language in `CONSENT_LOCALES` (the framework's seven plus French). The bundle
248
+ lives in `@owlmeans/consent` (`src/i18n/<locale>.json`); a new key goes into every one of them,
249
+ and that package's tests fail on a missing key or a dropped `{{placeholder}}` in any locale.
250
+ This package's `tests/bundle.spec.ts` reads every `t('…')` literal from the components, so a new
251
+ string without an English bundle entry fails there. A JSON-only edit is not re-emitted by an
252
+ incremental `tsc -b` — rebuild `@owlmeans/consent` with `tsc -b --force`, or consumers keep the old
253
+ bundle from `build/i18n`. **Once a `translate` prop is given,
254
+ the packaged bundle is not consulted at all**, so a wrapper that forwards a framework resolver alone
255
+ renders the English default for every key the application has not overridden, in every language. The
256
+ resolver must fall through to `defaultConsentTranslate(locale)` for the default — which is exactly
257
+ what `@owlmeans/web-panel/consent` does, and why an app inside the panel family mounts
258
+ `PanelCookieConsent` / `PanelCookiePolicy` rather than these components directly.
259
+
260
+ `ConsentToggle` is exported for a host that builds its own dialog body; the wording it shows is the
261
+ caller's, already resolved.
262
+
263
+ ## Depends On
264
+
265
+ - `@owlmeans/consent` — the model; the named selection above is re-exported from this package's root
266
+ - Peers (app-provided): `react`, `tailwindcss`, `tailwind-merge`, `clsx`, `lucide-react`
267
+
268
+ ## Related
269
+
270
+ - `consent` — the model: categories, `globalVar`, storage and migration, Consent Mode v2, the
271
+ ordering rule. Read it before changing anything a category means
272
+ - `web-gtm` — the head snippet that carries a stored decision to a Google tag, and
273
+ `googleTagServices(id)` for this page's `services`
274
+ - `astro` — stamping that snippet from a static site's layout
275
+ - `login-methods` / `login-plugins` — the sign-in precondition that raises this dialog with
276
+ `reason: 'login'`
277
+ - `web-panel` — its `./consent` subpath, the OwlMeans-i18n binding of these components
@@ -1,12 +1,4 @@
1
1
  import type { FC } from 'react';
2
2
  import type { CookieConsentProps } from '../types.js';
3
- /**
4
- * The cookie preferences dialog, and the button that brings it back.
5
- *
6
- * Deliberately built from raw elements rather than shadcn primitives. One of the three surfaces
7
- * this serves is an Astro island on a site that vendors its own component library, and requiring a
8
- * consumer to install a UI family in order to render a consent notice would put the notice out of
9
- * reach of the site that needs it most.
10
- */
11
3
  export declare const CookieConsent: FC<CookieConsentProps>;
12
4
  //# sourceMappingURL=component.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../src/consent/component.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAS/B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAErD;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,EAAE,EAAE,CAAC,kBAAkB,CA2HhD,CAAA"}
1
+ {"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../src/consent/component.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAS/B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAkCrD,eAAO,MAAM,aAAa,EAAE,EAAE,CAAC,kBAAkB,CA+HhD,CAAA"}
@@ -12,7 +12,22 @@ import { ConsentToggle } from './toggle.js';
12
12
  * this serves is an Astro island on a site that vendors its own component library, and requiring a
13
13
  * consumer to install a UI family in order to render a consent notice would put the notice out of
14
14
  * reach of the site that needs it most.
15
+ *
16
+ * Flat by rule, because it is the first thing every new visitor of a generated app sees: the
17
+ * surface and every colour come from the host's theme tokens (ground, ink, muted, hairline, one
18
+ * accent), depth comes from a hairline border and the overlay alone, and nothing paints a
19
+ * gradient, a shadow, a glow or a `backdrop-filter`. The accept action is the one accent pill,
20
+ * the others are outlined pills; every control is at least 44px tall and shows a 3px focus ring.
21
+ */
22
+ /**
23
+ * The focus ring every control here shows: 3px solid in the theme's ring colour, 3px off the
24
+ * element. An outline rather than a box-shadow ring, so it survives a host that zeroes shadows.
15
25
  */
26
+ const FOCUS = 'focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-ring';
27
+ /** A pill button: 44px tall at minimum, full width on a phone, sharing the row above that. */
28
+ const PILL = cn('inline-flex min-h-11 flex-1 items-center justify-center rounded-full px-6 py-2.5 text-[15px] transition-colors motion-safe:active:scale-[0.98]', FOCUS);
29
+ /** A text link: muted, underlined at rest (colour is never the only signal), 44px tall to tap. */
30
+ const LINK = cn('inline-flex min-h-11 items-center font-semibold underline decoration-1 underline-offset-4 transition-colors hover:text-foreground hover:decoration-2', FOCUS);
16
31
  export const CookieConsent = props => {
17
32
  const categories = props.categories ?? DEFAULT_CONSENT_CATEGORIES;
18
33
  const t = props.translate ?? defaultConsentTranslate(props.locale);
@@ -49,8 +64,8 @@ export const CookieConsent = props => {
49
64
  // today. Saying so, and relabelling the primary action, is what makes the interruption make
50
65
  // sense rather than look like the page asking twice.
51
66
  const gated = consent.reason === 'login';
52
- return _jsxs(_Fragment, { children: [consent.open && _jsx("div", { className: "fixed inset-0 z-[999998] flex items-center justify-center overflow-y-auto bg-black/70 px-4 py-6 backdrop-blur-md", "aria-modal": "true", role: "dialog", "aria-labelledby": "cc-title", "aria-describedby": "cc-desc", "data-consent-dialog": true, children: _jsxs("div", { className: cn('relative z-[999999] w-full max-w-lg rounded-2xl border border-border bg-popover/95 p-6 shadow-[0_25px_60px_-12px_rgba(0,0,0,0.5)] ring-1 ring-primary/20 backdrop-blur-xl sm:p-8', props.className), children: [_jsx("div", { className: "absolute inset-x-0 top-0 h-1 rounded-t-2xl bg-gradient-to-r from-primary to-secondary" }), _jsxs("div", { className: "mb-4 flex items-center gap-3", children: [_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-xl bg-primary/10 text-primary", children: _jsx(Cookie, { className: "h-5 w-5", "aria-hidden": "true" }) }), _jsx("h2", { id: "cc-title", className: "text-xl font-bold text-popover-foreground sm:text-2xl", children: t('title', 'Cookie Preferences') })] }), _jsx("p", { id: "cc-desc", className: "mb-6 text-sm leading-relaxed text-muted-foreground", children: t('description', 'We use cookies to enhance your browsing experience, serve personalized ads or content, and analyze our traffic.') }), _jsx("div", { className: "space-y-3", children: categories.map(category => _jsx(ConsentToggle, { id: `cc-${category.key}`, label: t(category.labelKey, category.key), description: t(category.descriptionKey, ''), checked: draft[category.key] === true, ...(category.required === true ? { required: true } : {}), requiredLabel: t('required', 'Required'), onChange: value => setDraft(current => ({ ...current, [category.key]: value })) }, category.key)) }), gated && _jsx("p", { role: "status", "data-consent-reason": true, className: "mt-4 text-sm text-popover-foreground", children: t('loginReason', 'Signing in stores a session cookie. Accept essential cookies to continue.') }), _jsxs("div", { className: "mt-8 flex flex-col gap-3 sm:flex-row sm:gap-4", children: [_jsx("button", { type: "button", onClick: onSave, "data-consent-save": true, className: "flex-1 rounded-lg border border-border bg-transparent px-4 py-3 text-sm font-semibold text-popover-foreground transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: t('savePreferences', 'Save Preferences') }), _jsx("button", { type: "button", onClick: onAcceptAll, "data-consent-accept-all": true, className: "flex-1 rounded-lg bg-primary px-4 py-3 text-sm font-bold text-primary-foreground shadow-lg shadow-primary/25 transition-all hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", children: gated
67
+ return _jsxs(_Fragment, { children: [consent.open && _jsx("div", { className: "fixed inset-0 z-[999998] flex items-center justify-center overflow-y-auto bg-black/70 px-4 py-6", "aria-modal": "true", role: "dialog", "aria-labelledby": "cc-title", "aria-describedby": "cc-desc", "data-consent-dialog": true, children: _jsxs("div", { className: cn('relative z-[999999] w-full max-w-lg rounded-3xl border border-border bg-background p-6 text-foreground sm:p-8', props.className), children: [_jsxs("div", { className: "mb-4 flex items-center gap-3", children: [_jsx("div", { className: "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full bg-muted text-primary", children: _jsx(Cookie, { className: "h-5 w-5", "aria-hidden": "true" }) }), _jsx("h2", { id: "cc-title", className: "text-balance text-xl font-extrabold tracking-tight text-foreground sm:text-2xl", children: t('title', 'Cookie Preferences') })] }), _jsx("p", { id: "cc-desc", className: "mb-6 text-pretty text-sm leading-relaxed text-muted-foreground", children: t('description', 'We use cookies to enhance your browsing experience, serve personalized ads or content, and analyze our traffic.') }), _jsx("div", { className: "space-y-3", children: categories.map(category => _jsx(ConsentToggle, { id: `cc-${category.key}`, label: t(category.labelKey, category.key), description: t(category.descriptionKey, ''), checked: draft[category.key] === true, ...(category.required === true ? { required: true } : {}), requiredLabel: t('required', 'Required'), onChange: value => setDraft(current => ({ ...current, [category.key]: value })) }, category.key)) }), gated && _jsx("p", { role: "status", "data-consent-reason": true, className: "mt-4 text-sm font-semibold text-foreground", children: t('loginReason', 'Signing in stores a session cookie. Accept essential cookies to continue.') }), _jsxs("div", { className: "mt-8 flex flex-col gap-3 sm:flex-row sm:gap-4", children: [_jsx("button", { type: "button", onClick: onSave, "data-consent-save": true, className: cn(PILL, 'border-[1.5px] border-foreground bg-transparent font-semibold text-foreground hover:bg-muted'), children: t('savePreferences', 'Save Preferences') }), _jsx("button", { type: "button", onClick: onAcceptAll, "data-consent-accept-all": true, className: cn(PILL, 'bg-primary font-bold text-primary-foreground hover:bg-primary/90'), children: gated
53
68
  ? t('acceptAndContinue', 'Accept & continue')
54
- : t('acceptAll', 'Accept All') })] }), (props.policyHref != null || props.links != null) && _jsxs("div", { className: "mt-4 flex flex-wrap justify-center gap-3 text-xs text-muted-foreground", children: [props.policyHref != null && _jsx("a", { href: props.policyHref, target: "_blank", rel: "noopener noreferrer", className: "underline underline-offset-2 transition-colors hover:text-popover-foreground", children: t('policyLink', 'Cookie Policy') }), props.links?.map(link => _jsx("a", { href: link.href, target: "_blank", rel: "noopener noreferrer", className: "underline underline-offset-2 transition-colors hover:text-popover-foreground", children: t(link.labelKey, link.defaultLabel) }, link.href))] })] }) }), props.noReopenButton !== true && consent.record != null && !consent.open && _jsx("button", { type: "button", onClick: () => consent.openDialog('reopen'), "aria-label": t('openPreferences', 'Cookie preferences'), "data-consent-reopen": true, className: "fixed bottom-5 left-5 z-[999997] flex h-12 w-12 items-center justify-center rounded-full border border-border bg-popover text-primary shadow-lg transition-transform hover:scale-110 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: _jsx(Cookie, { className: "h-5 w-5", "aria-hidden": "true" }) })] });
69
+ : t('acceptAll', 'Accept All') })] }), (props.policyHref != null || props.links != null) && _jsxs("div", { className: "mt-3 flex flex-wrap justify-center gap-x-4 text-[13px] text-muted-foreground", children: [props.policyHref != null && _jsx("a", { href: props.policyHref, target: "_blank", rel: "noopener noreferrer", className: LINK, children: t('policyLink', 'Cookie Policy') }), props.links?.map(link => _jsx("a", { href: link.href, target: "_blank", rel: "noopener noreferrer", className: LINK, children: t(link.labelKey, link.defaultLabel) }, link.href))] })] }) }), props.noReopenButton !== true && consent.record != null && !consent.open && _jsx("button", { type: "button", onClick: () => consent.openDialog('reopen'), "aria-label": t('openPreferences', 'Cookie preferences'), "data-consent-reopen": true, className: cn('fixed bottom-1 left-1 z-[999997] inline-flex h-11 w-11 items-center justify-center rounded border-0 bg-transparent p-0 text-muted-foreground opacity-70 transition-opacity hover:opacity-100 hover:text-primary focus-visible:opacity-100', FOCUS), children: _jsx(Cookie, { className: "h-5 w-5", "aria-hidden": "true" }) })] });
55
70
  };
56
71
  //# sourceMappingURL=component.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"component.js","sourceRoot":"","sources":["../../src/consent/component.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"component.js","sourceRoot":"","sources":["../../src/consent/component.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAExD,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AACrC,OAAO,EACL,0BAA0B,EAAE,uBAAuB,EAAE,WAAW,GACjE,MAAM,mBAAmB,CAAA;AAE1B,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAA;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAG3C;;;;;;;;;;;;;GAaG;AACH;;;GAGG;AACH,MAAM,KAAK,GAAG,mFAAmF,CAAA;AAEjG,8FAA8F;AAC9F,MAAM,IAAI,GAAG,EAAE,CACb,gJAAgJ,EAChJ,KAAK,CACN,CAAA;AAED,kGAAkG;AAClG,MAAM,IAAI,GAAG,EAAE,CACb,sJAAsJ,EACtJ,KAAK,CACN,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAA2B,KAAK,CAAC,EAAE;IAC3D,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,IAAI,0BAA0B,CAAA;IACjE,MAAM,CAAC,GAAG,KAAK,CAAC,SAAS,IAAI,uBAAuB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;IAElE,MAAM,OAAO,GAAG,UAAU,CAAC;QACzB,UAAU;QACV,GAAG,CAAC,KAAK,CAAC,UAAU,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrE,GAAG,CAAC,KAAK,CAAC,UAAU,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrE,GAAG,CAAC,KAAK,CAAC,YAAY,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3E,GAAG,CAAC,KAAK,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1D,CAAC,CAAA;IAEF,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAA;IAC1E,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAA0B,EAAE,CAAC,CAAA;IAE/D,iGAAiG;IACjG,6FAA6F;IAC7F,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAClB,OAAM;QACR,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,WAAW,CAAC;YAC3C,GAAG,CAAC,KAAK,CAAC,UAAU,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACtE,CAAC,CAAA;QACF,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAClD,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;IACtD,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAA;IAElC,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,MAA+B,EAAE,EAAE;QAC9D,MAAM,MAAM,GAAkB,MAAM,CAAC,WAAW,CAAC;YAC/C,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACtE,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC;SACtD,CAAC,CAAA;QACF,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IACtB,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAA;IAEzB,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAA;IAClE,MAAM,WAAW,GAAG,WAAW,CAC7B,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CACzF,CAAA;IAED,+FAA+F;IAC/F,4FAA4F;IAC5F,qDAAqD;IACrD,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,KAAK,OAAO,CAAA;IAExC,OAAO,8BACJ,OAAO,CAAC,IAAI,IAAI,cACf,SAAS,EAAC,iGAAiG,gBAChG,MAAM,EAAC,IAAI,EAAC,QAAQ,qBAAiB,UAAU,sBAAkB,SAAS,yCAGrF,eAAK,SAAS,EAAE,EAAE,CAChB,+GAA+G,EAC/G,KAAK,CAAC,SAAS,CAChB,aACC,eAAK,SAAS,EAAC,8BAA8B,aAC3C,cAAK,SAAS,EAAC,6FAA6F,YAC1G,KAAC,MAAM,IAAC,SAAS,EAAC,SAAS,iBAAa,MAAM,GAAG,GAC7C,EACN,aAAI,EAAE,EAAC,UAAU,EAAC,SAAS,EAAC,gFAAgF,YACzG,CAAC,CAAC,OAAO,EAAE,oBAAoB,CAAC,GAC9B,IACD,EAEN,YAAG,EAAE,EAAC,SAAS,EAAC,SAAS,EAAC,gEAAgE,YACvF,CAAC,CAAC,aAAa,EAAE,iHAAiH,CAAC,GAClI,EAEJ,cAAK,SAAS,EAAC,WAAW,YACvB,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,KAAC,aAAa,IAExC,EAAE,EAAE,MAAM,QAAQ,CAAC,GAAG,EAAE,EACxB,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,EACzC,WAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,cAAc,EAAE,EAAE,CAAC,EAC3C,OAAO,EAAE,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,IAAI,KACjC,CAAC,QAAQ,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAC1D,aAAa,EAAE,CAAC,CAAC,UAAU,EAAE,UAAU,CAAC,EACxC,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,IAP1E,QAAQ,CAAC,GAAG,CAQjB,CAAC,GACC,EAEL,KAAK,IAAI,YAAG,IAAI,EAAC,QAAQ,+BAAqB,SAAS,EAAC,4CAA4C,YAClG,CAAC,CAAC,aAAa,EAAE,2EAA2E,CAAC,GAC5F,EAEJ,eAAK,SAAS,EAAC,+CAA+C,aAC5D,iBACE,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAE,MAAM,6BAC7B,SAAS,EAAE,EAAE,CAAC,IAAI,EAAE,8FAA8F,CAAC,YACnH,CAAC,CAAC,iBAAiB,EAAE,kBAAkB,CAAC,GAAU,EACpD,iBACE,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAE,WAAW,mCAClC,SAAS,EAAE,EAAE,CAAC,IAAI,EAAE,kEAAkE,CAAC,YACvF,KAAK;wCACL,CAAC,CAAC,CAAC,CAAC,mBAAmB,EAAE,mBAAmB,CAAC;wCAC7C,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,YAAY,CAAC,GAAU,IACtC,EAEL,CAAC,KAAK,CAAC,UAAU,IAAI,IAAI,IAAI,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,IAAI,eAAK,SAAS,EAAC,8EAA8E,aAChJ,KAAK,CAAC,UAAU,IAAI,IAAI,IAAI,YAC3B,IAAI,EAAE,KAAK,CAAC,UAAU,EAAE,MAAM,EAAC,QAAQ,EAAC,GAAG,EAAC,qBAAqB,EAAC,SAAS,EAAE,IAAI,YACjF,CAAC,CAAC,YAAY,EAAE,eAAe,CAAC,GAAK,EACtC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,YACR,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAC,QAAQ,EAAC,GAAG,EAAC,qBAAqB,EAAC,SAAS,EAAE,IAAI,YAC1F,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,IAD9B,IAAI,CAAC,IAAI,CAC0B,CAAC,IACvC,IACF,GACF,EAUL,KAAK,CAAC,cAAc,KAAK,IAAI,IAAI,OAAO,CAAC,MAAM,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,iBAC3E,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,gBAC7C,CAAC,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,+BAEtD,SAAS,EAAE,EAAE,CAAC,2OAA2O,EAAE,KAAK,CAAC,YAEjQ,KAAC,MAAM,IAAC,SAAS,EAAC,SAAS,iBAAa,MAAM,GAAG,GAC1C,IACR,CAAA;AACL,CAAC,CAAA"}
@@ -9,5 +9,20 @@ export interface ConsentToggleProps {
9
9
  requiredLabel: string;
10
10
  onChange: (value: boolean) => void;
11
11
  }
12
+ /**
13
+ * One category row: a hairline card with the label, the description and a switch.
14
+ *
15
+ * Flat like the dialog it sits in — no tinted fill, no shadow, no badge. `Required` is plain muted
16
+ * text beside the label, because a required category is disclosure, not a status to decorate. The
17
+ * switch's hit area is 44px square around the visible 24×44 track, and it shows the dialog's 3px
18
+ * focus ring when reached from the keyboard (the real checkbox is visually hidden, so the ring is
19
+ * drawn on the track through `peer-focus-visible`).
20
+ *
21
+ * The checkbox takes its NAME from the category label and its description from the `Required`
22
+ * marker and the description text, by id. The `<label>` around it holds only the drawn track, so
23
+ * without those references a screen reader announces an unnamed checkbox three times over — the
24
+ * visible words sit in a sibling column the label cannot reach. A required category keeps its name
25
+ * while disabled: it is still disclosed, only not a choice.
26
+ */
12
27
  export declare const ConsentToggle: FC<ConsentToggleProps>;
13
28
  //# sourceMappingURL=toggle.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"toggle.d.ts","sourceRoot":"","sources":["../../src/consent/toggle.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAG/B,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;IAChB,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CACnC;AAED,eAAO,MAAM,aAAa,EAAE,EAAE,CAAC,kBAAkB,CAgChD,CAAA"}
1
+ {"version":3,"file":"toggle.d.ts","sourceRoot":"","sources":["../../src/consent/toggle.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAG/B,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;IAChB,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CACnC;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,aAAa,EAAE,EAAE,CAAC,kBAAkB,CA+ChD,CAAA"}
@@ -1,4 +1,25 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cn } from '../lib/utils.js';
3
- export const ConsentToggle = ({ id, label, description, checked, required, requiredLabel, onChange }) => (_jsxs("div", { className: "flex items-start justify-between rounded-lg border border-border/50 bg-muted/30 p-3", children: [_jsxs("div", { className: "flex-1 pr-4", children: [_jsxs("div", { className: "mb-0.5 flex items-center gap-2 text-sm font-semibold text-popover-foreground", children: [label, required === true && _jsx("span", { className: "rounded bg-primary/10 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-primary", children: requiredLabel })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: description })] }), _jsxs("label", { className: cn('relative h-6 w-11 flex-shrink-0', required === true ? 'cursor-not-allowed opacity-70' : 'cursor-pointer'), children: [_jsx("input", { id: id, type: "checkbox", className: "sr-only", checked: checked || required === true, disabled: required === true, onChange: event => onChange(event.target.checked) }), _jsx("span", { className: cn('absolute inset-0 rounded-full transition-colors duration-300', checked || required === true ? 'bg-primary' : 'bg-muted-foreground/30') }), _jsx("span", { className: cn('absolute top-0.5 h-5 w-5 rounded-full bg-white shadow transition-transform duration-300', checked || required === true ? 'translate-x-5' : 'translate-x-0.5') })] })] }));
3
+ /**
4
+ * One category row: a hairline card with the label, the description and a switch.
5
+ *
6
+ * Flat like the dialog it sits in — no tinted fill, no shadow, no badge. `Required` is plain muted
7
+ * text beside the label, because a required category is disclosure, not a status to decorate. The
8
+ * switch's hit area is 44px square around the visible 24×44 track, and it shows the dialog's 3px
9
+ * focus ring when reached from the keyboard (the real checkbox is visually hidden, so the ring is
10
+ * drawn on the track through `peer-focus-visible`).
11
+ *
12
+ * The checkbox takes its NAME from the category label and its description from the `Required`
13
+ * marker and the description text, by id. The `<label>` around it holds only the drawn track, so
14
+ * without those references a screen reader announces an unnamed checkbox three times over — the
15
+ * visible words sit in a sibling column the label cannot reach. A required category keeps its name
16
+ * while disabled: it is still disclosed, only not a choice.
17
+ */
18
+ export const ConsentToggle = ({ id, label, description, checked, required, requiredLabel, onChange }) => {
19
+ const described = [
20
+ ...(required === true ? [`${id}-required`] : []),
21
+ ...(description !== '' ? [`${id}-desc`] : []),
22
+ ];
23
+ return (_jsxs("div", { className: "flex items-center justify-between gap-4 rounded-2xl border border-border p-4", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "mb-0.5 flex flex-wrap items-center gap-x-2 text-sm font-bold text-foreground", children: [_jsx("span", { id: `${id}-label`, children: label }), required === true && _jsx("span", { id: `${id}-required`, className: "text-xs font-bold uppercase tracking-wide text-muted-foreground", children: requiredLabel })] }), description !== '' && _jsx("p", { id: `${id}-desc`, className: "text-pretty text-xs leading-relaxed text-muted-foreground", children: description })] }), _jsxs("label", { className: cn('relative inline-flex h-11 w-14 flex-shrink-0 items-center justify-center', required === true ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'), children: [_jsx("input", { id: id, type: "checkbox", "aria-labelledby": `${id}-label`, ...(described.length > 0 ? { 'aria-describedby': described.join(' ') } : {}), className: "peer sr-only", checked: checked || required === true, disabled: required === true, onChange: event => onChange(event.target.checked) }), _jsx("span", { className: cn('relative h-6 w-11 rounded-full transition-colors duration-300 peer-focus-visible:outline-3 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-ring', checked || required === true ? 'bg-primary' : 'bg-muted-foreground/40'), children: _jsx("span", { className: cn('absolute top-0.5 left-0 h-5 w-5 rounded-full bg-white transition-transform duration-300', checked || required === true ? 'translate-x-5' : 'translate-x-0.5') }) })] })] }));
24
+ };
4
25
  //# sourceMappingURL=toggle.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"toggle.js","sourceRoot":"","sources":["../../src/consent/toggle.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAA;AAapC,MAAM,CAAC,MAAM,aAAa,GAA2B,CACnD,EAAE,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,EACtE,EAAE,CAAC,CACH,eAAK,SAAS,EAAC,qFAAqF,aAClG,eAAK,SAAS,EAAC,aAAa,aAC1B,eAAK,SAAS,EAAC,8EAA8E,aAC1F,KAAK,EACL,QAAQ,KAAK,IAAI,IAAI,eAAM,SAAS,EAAC,kGAAkG,YACrI,aAAa,GACT,IACH,EACN,YAAG,SAAS,EAAC,+BAA+B,YAAE,WAAW,GAAK,IAC1D,EACN,iBAAO,SAAS,EAAE,EAAE,CAAC,iCAAiC,EAAE,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAC,CAAC,gBAAgB,CAAC,aAC7H,gBACE,EAAE,EAAE,EAAE,EACN,IAAI,EAAC,UAAU,EACf,SAAS,EAAC,SAAS,EACnB,OAAO,EAAE,OAAO,IAAI,QAAQ,KAAK,IAAI,EACrC,QAAQ,EAAE,QAAQ,KAAK,IAAI,EAC3B,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,GACjD,EACF,eAAM,SAAS,EAAE,EAAE,CACjB,8DAA8D,EAC9D,OAAO,IAAI,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,wBAAwB,CACvE,GAAI,EACL,eAAM,SAAS,EAAE,EAAE,CACjB,yFAAyF,EACzF,OAAO,IAAI,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,iBAAiB,CACnE,GAAI,IACC,IACJ,CACP,CAAA"}
1
+ {"version":3,"file":"toggle.js","sourceRoot":"","sources":["../../src/consent/toggle.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAA;AAapC;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,aAAa,GAA2B,CACnD,EAAE,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,EACtE,EAAE;IACF,MAAM,SAAS,GAAG;QAChB,GAAG,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAChD,GAAG,CAAC,WAAW,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;KAC9C,CAAA;IAED,OAAO,CACL,eAAK,SAAS,EAAC,8EAA8E,aAC3F,eAAK,SAAS,EAAC,gBAAgB,aAC7B,eAAK,SAAS,EAAC,8EAA8E,aAC3F,eAAM,EAAE,EAAE,GAAG,EAAE,QAAQ,YAAG,KAAK,GAAQ,EACtC,QAAQ,KAAK,IAAI,IAAI,eACpB,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE,SAAS,EAAC,iEAAiE,YACjG,aAAa,GAAQ,IACnB,EACL,WAAW,KAAK,EAAE,IAAI,YACrB,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,SAAS,EAAC,2DAA2D,YACvF,WAAW,GAAK,IACd,EACN,iBAAO,SAAS,EAAE,EAAE,CAClB,0EAA0E,EAC1E,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAC,CAAC,gBAAgB,CACvE,aACC,gBACE,EAAE,EAAE,EAAE,EACN,IAAI,EAAC,UAAU,qBACE,GAAG,EAAE,QAAQ,KAC1B,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,kBAAkB,EAAE,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAC7E,SAAS,EAAC,cAAc,EACxB,OAAO,EAAE,OAAO,IAAI,QAAQ,KAAK,IAAI,EACrC,QAAQ,EAAE,QAAQ,KAAK,IAAI,EAC3B,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,GACjD,EACF,eAAM,SAAS,EAAE,EAAE,CACjB,gKAAgK,EAChK,OAAO,IAAI,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,wBAAwB,CACvE,YACC,eAAM,SAAS,EAAE,EAAE,CACjB,yFAAyF,EACzF,OAAO,IAAI,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,iBAAiB,CACnE,GAAI,GACA,IACD,IACJ,CACP,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,18 @@
1
+ import type { FC } from 'react';
2
+ import type { ConsentMenuWidgetProps } from '../types.js';
3
+ /**
4
+ * One row a HOST'S OWN menu can render to reopen the preferences dialog, in place of (or beside)
5
+ * the floating button `CookieConsent` renders itself. No outer row padding here — the host's own
6
+ * menu-row wrapper supplies that, the same way it does for any other widget row it renders.
7
+ *
8
+ * Carries no presence signalling of its own: a host whose menu only mounts this row while its
9
+ * dropdown/menu content is actually open (Radix's `DropdownMenuContent`, for one) would report
10
+ * "present" only for that brief window. A host that wants to hide `CookieConsent`'s floating
11
+ * button while this row is reachable announces that from whatever component of its own stays
12
+ * mounted for the menu's whole lifetime — see `useConsentMenuPresence` in `@owlmeans/web-panel/consent`.
13
+ *
14
+ * Uses `openConsent` directly rather than the `useConsent()` hook: that hook's mount itself
15
+ * triggers `consentStore.init()`, which a row that only needs to trigger a reopen must not do.
16
+ */
17
+ export declare const ConsentMenuWidget: FC<ConsentMenuWidgetProps>;
18
+ //# sourceMappingURL=widget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget.d.ts","sourceRoot":"","sources":["../../src/consent/widget.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAI/B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAEzD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,iBAAiB,EAAE,EAAE,CAAC,sBAAsB,CAqBxD,CAAA"}
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback } from 'react';
3
+ import { Cookie } from 'lucide-react';
4
+ import { defaultConsentTranslate, openConsent } from '@owlmeans/consent';
5
+ import { cn } from '../lib/utils.js';
6
+ /**
7
+ * One row a HOST'S OWN menu can render to reopen the preferences dialog, in place of (or beside)
8
+ * the floating button `CookieConsent` renders itself. No outer row padding here — the host's own
9
+ * menu-row wrapper supplies that, the same way it does for any other widget row it renders.
10
+ *
11
+ * Carries no presence signalling of its own: a host whose menu only mounts this row while its
12
+ * dropdown/menu content is actually open (Radix's `DropdownMenuContent`, for one) would report
13
+ * "present" only for that brief window. A host that wants to hide `CookieConsent`'s floating
14
+ * button while this row is reachable announces that from whatever component of its own stays
15
+ * mounted for the menu's whole lifetime — see `useConsentMenuPresence` in `@owlmeans/web-panel/consent`.
16
+ *
17
+ * Uses `openConsent` directly rather than the `useConsent()` hook: that hook's mount itself
18
+ * triggers `consentStore.init()`, which a row that only needs to trigger a reopen must not do.
19
+ */
20
+ export const ConsentMenuWidget = props => {
21
+ const t = props.translate ?? defaultConsentTranslate(props.locale);
22
+ const onClick = useCallback(() => {
23
+ if (props.onSelect != null) {
24
+ props.onSelect();
25
+ return;
26
+ }
27
+ openConsent('reopen');
28
+ }, [props.onSelect]);
29
+ return _jsxs("button", { type: "button", onClick: onClick, "data-consent-menu-widget": true, className: cn('flex w-full min-w-0 items-center gap-2 rounded-md text-left text-sm text-popover-foreground transition-colors hover:bg-accent', props.className), children: [_jsx(Cookie, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }), _jsx("span", { className: "flex-1 truncate", children: props.label ?? t('openPreferences', 'Cookie preferences') })] });
30
+ };
31
+ //# sourceMappingURL=widget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget.js","sourceRoot":"","sources":["../../src/consent/widget.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAEnC,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AACrC,OAAO,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACxE,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAA;AAGpC;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAA+B,KAAK,CAAC,EAAE;IACnE,MAAM,CAAC,GAAG,KAAK,CAAC,SAAS,IAAI,uBAAuB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;IAElE,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE;QAC/B,IAAI,KAAK,CAAC,QAAQ,IAAI,IAAI,EAAE,CAAC;YAC3B,KAAK,CAAC,QAAQ,EAAE,CAAA;YAChB,OAAM;QACR,CAAC;QACD,WAAW,CAAC,QAAQ,CAAC,CAAA;IACvB,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAA;IAEpB,OAAO,kBACL,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAE,OAAO,oCAC9B,SAAS,EAAE,EAAE,CACX,+HAA+H,EAC/H,KAAK,CAAC,SAAS,CAChB,aAED,KAAC,MAAM,IAAC,SAAS,EAAC,kBAAkB,iBAAa,MAAM,GAAG,EAC1D,eAAM,SAAS,EAAC,iBAAiB,YAAE,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,GAAQ,IAC7F,CAAA;AACX,CAAC,CAAA"}
package/build/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  export type * from './types.js';
2
2
  export * from './hooks.js';
3
3
  export * from './consent/component.js';
4
+ export * from './consent/widget.js';
4
5
  export * from './consent/toggle.js';
5
6
  export * from './policy/component.js';
6
7
  export { consentStore, openConsent, isConsented, readConsent, writeConsent, clearConsent, migrateConsent, applyConsent, pushConsentDefaults, consentBootstrapScript, consentDefaults, consentUpdate, gtagConsent, DEFAULT_CONSENT_CATEGORIES, DEFAULT_CONSENT_MESSAGES, defaultConsentTranslate, interpolate, CONSENT_KEY, CONSENT_COOKIE_DAYS, CONSENT_SCHEMA_VERSION, CONSENT_LOCALES, CONSENT_ESSENTIAL, CONSENT_ANALYTICS, CONSENT_MARKETING, } from '@owlmeans/consent';
7
- export type { ConsentCategory, ConsentOptions, ConsentReason, ConsentRecord, ConsentSignal, ConsentState, ConsentStore, ConsentLocale, } from '@owlmeans/consent';
8
+ export type { ConsentCategory, ConsentOptions, ConsentReason, ConsentRecord, ConsentService, ConsentSignal, ConsentState, ConsentStore, ConsentLocale, } from '@owlmeans/consent';
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,YAAY,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,cAAc,wBAAwB,CAAA;AACtC,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AAErC,OAAO,EACL,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAC/E,cAAc,EAAE,YAAY,EAAE,mBAAmB,EAAE,sBAAsB,EACzE,eAAe,EAAE,aAAa,EAAE,WAAW,EAC3C,0BAA0B,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,WAAW,EAC1F,WAAW,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,eAAe,EACzE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,GACxD,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EACV,eAAe,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAC1F,YAAY,EAAE,aAAa,GAC5B,MAAM,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,YAAY,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,cAAc,wBAAwB,CAAA;AACtC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AAErC,OAAO,EACL,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAC/E,cAAc,EAAE,YAAY,EAAE,mBAAmB,EAAE,sBAAsB,EACzE,eAAe,EAAE,aAAa,EAAE,WAAW,EAC3C,0BAA0B,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,WAAW,EAC1F,WAAW,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,eAAe,EACzE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,GACxD,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EACV,eAAe,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,EAC5F,YAAY,EAAE,YAAY,EAAE,aAAa,GAC1C,MAAM,mBAAmB,CAAA"}
package/build/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './hooks.js';
2
2
  export * from './consent/component.js';
3
+ export * from './consent/widget.js';
3
4
  export * from './consent/toggle.js';
4
5
  export * from './policy/component.js';
5
6
  export { consentStore, openConsent, isConsented, readConsent, writeConsent, clearConsent, migrateConsent, applyConsent, pushConsentDefaults, consentBootstrapScript, consentDefaults, consentUpdate, gtagConsent, DEFAULT_CONSENT_CATEGORIES, DEFAULT_CONSENT_MESSAGES, defaultConsentTranslate, interpolate, CONSENT_KEY, CONSENT_COOKIE_DAYS, CONSENT_SCHEMA_VERSION, CONSENT_LOCALES, CONSENT_ESSENTIAL, CONSENT_ANALYTICS, CONSENT_MARKETING, } from '@owlmeans/consent';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAA;AAC1B,cAAc,wBAAwB,CAAA;AACtC,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AAErC,OAAO,EACL,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAC/E,cAAc,EAAE,YAAY,EAAE,mBAAmB,EAAE,sBAAsB,EACzE,eAAe,EAAE,aAAa,EAAE,WAAW,EAC3C,0BAA0B,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,WAAW,EAC1F,WAAW,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,eAAe,EACzE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,GACxD,MAAM,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAA;AAC1B,cAAc,wBAAwB,CAAA;AACtC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AAErC,OAAO,EACL,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAC/E,cAAc,EAAE,YAAY,EAAE,mBAAmB,EAAE,sBAAsB,EACzE,eAAe,EAAE,aAAa,EAAE,WAAW,EAC3C,0BAA0B,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,WAAW,EAC1F,WAAW,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,eAAe,EACzE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,GACxD,MAAM,mBAAmB,CAAA"}
@@ -11,6 +11,12 @@ import type { CookiePolicyProps } from '../types.js';
11
11
  *
12
12
  * Everything OwlMeans cannot assert on the operator's behalf — who the controller is, what the
13
13
  * lawful basis is, how to exercise rights — is deferred to their own privacy policy and terms.
14
+ *
15
+ * `services` names WHO receives data under each category. It is the one input the widget cannot
16
+ * derive, so it comes from whatever added the tag (`googleTagServices(id)` for a Google tag), and
17
+ * a service is never dropped for want of a matching category: one whose category is not in force
18
+ * is listed in a trailing group, because a service the page runs and does not disclose is worse
19
+ * than one disclosed in the wrong place.
14
20
  */
15
21
  export declare const CookiePolicy: FC<CookiePolicyProps>;
16
22
  //# sourceMappingURL=component.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../src/policy/component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAM/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAEpD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,YAAY,EAAE,EAAE,CAAC,iBAAiB,CAmD9C,CAAA"}
1
+ {"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../src/policy/component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAO/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAkDpD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,YAAY,EAAE,EAAE,CAAC,iBAAiB,CAsE9C,CAAA"}