From f31d91265a3ba04f1bf62c0a5fad341b2dbd7bb3 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Tue, 30 Jun 2026 17:30:11 +0800 Subject: [PATCH] docs: document cookie consent lifecycle event API --- projects/packages/cookie-consent/README.md | 51 +++++++++++++++++++ .../cookie-consent/changelog/auto-WOOA7S-1598 | 4 ++ .../cookie-consent/tests/utils.test.ts | 48 +++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 projects/packages/cookie-consent/changelog/auto-WOOA7S-1598 diff --git a/projects/packages/cookie-consent/README.md b/projects/packages/cookie-consent/README.md index 3ec3c48f3107..38a78be82729 100644 --- a/projects/packages/cookie-consent/README.md +++ b/projects/packages/cookie-consent/README.md @@ -89,6 +89,57 @@ add_filter( ); ``` +## Public APIs + +### Gating scripts on consent + +Consumers that need to gate their own scripts on visitor consent should use the +WP Consent API directly, not a Cookie Consent lifecycle event: + +- JavaScript: call `window.wp_has_consent( category )` for the initial state and + listen for the `wp_listen_for_consent_change` DOM event for changes. +- PHP: call `wp_has_consent( category )` before rendering or enqueueing gated + server-side output. + +The canonical integration pattern is `woocommerce-analytics`: it gates tracking +with the WP Consent API state and change event because those APIs model consent +categories across providers. + +### `wp_consent_saved` + +Cookie Consent dispatches `wp_consent_saved` on `window` after it writes a +visitor choice through the WP Consent API. This event is public API and follows +the package's backward-compatibility policy for documented APIs. + +```js +window.addEventListener( 'wp_consent_saved', event => { + const { eventType, choices } = event.detail; +} ); +``` + +The event detail has this stable shape: + +```ts +type CookieConsentSavedDetail = { + eventType: + | 'accept_all' + | 'accept_selected' + | 'reject_all' + | 'auto_granted' + | 'opt-out'; + choices: Partial< Record< string, boolean > >; +}; +``` + +`choices` is keyed by Cookie Consent category keys (`consent.categories`; +currently `analytics` and `advertising`), not raw WP Consent API category names, +and each present value indicates whether that category was allowed. Use +`eventType` when you need to distinguish the user action behind the saved choice. +Use the WP Consent API for category-state gating. + +`wp_consent_type_defined` remains an internal implementation event and is not +part of the public API surface. + ## Theming and customization The banner, modal, category toggles, and footer-links fallback control are styled from namespaced CSS custom properties (design tokens) with self-contained defaults, so they render consistently regardless of the active theme. The tokens are deliberately **not** derived from theme presets (`--wp--preset--*`): a theme that defines those presets for its own layout (a small spacing scale, an inverted palette, etc.) cannot break or recolor the consent UI. diff --git a/projects/packages/cookie-consent/changelog/auto-WOOA7S-1598 b/projects/packages/cookie-consent/changelog/auto-WOOA7S-1598 new file mode 100644 index 000000000000..59cd342a486c --- /dev/null +++ b/projects/packages/cookie-consent/changelog/auto-WOOA7S-1598 @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +Document the public cookie consent lifecycle event and WP Consent API script-gating guidance. diff --git a/projects/packages/cookie-consent/tests/utils.test.ts b/projects/packages/cookie-consent/tests/utils.test.ts index dcc3ff470160..5eac28d05180 100644 --- a/projects/packages/cookie-consent/tests/utils.test.ts +++ b/projects/packages/cookie-consent/tests/utils.test.ts @@ -11,6 +11,7 @@ import { handleConsentByRegion, isGdprCountry, pertainsToCCPA, + saveConsentChoices, setCookie, } from '../src/modules/cookie-consent/utils'; @@ -203,6 +204,53 @@ describe( 'handleConsentByRegion (GDPR + GPC)', () => { } ); } ); +describe( 'saveConsentChoices', () => { + let consentCalls: Array< [ string, string ] >; + + beforeEach( () => { + consentCalls = []; + window.wp_set_consent = ( category: string, state: string ) => { + consentCalls.push( [ category, state ] ); + }; + } ); + + afterEach( () => { + delete ( window as unknown as { wp_set_consent?: unknown } ).wp_set_consent; + } ); + + it( 'dispatches the public wp_consent_saved event with event type and category choices', () => { + let savedEvent: CustomEvent | undefined; + const listener = ( event: Event ) => { + savedEvent = event as CustomEvent; + }; + window.addEventListener( 'wp_consent_saved', listener ); + + saveConsentChoices( + { + analytics: true, + advertising: false, + }, + 'accept_selected' + ); + + window.removeEventListener( 'wp_consent_saved', listener ); + + expect( consentCalls ).toEqual( [ + [ 'functional', 'allow' ], + [ 'statistics', 'allow' ], + [ 'statistics-anonymous', 'allow' ], + [ 'marketing', 'deny' ], + ] ); + expect( savedEvent?.detail ).toEqual( { + eventType: 'accept_selected', + choices: { + analytics: true, + advertising: false, + }, + } ); + } ); +} ); + describe( 'geo configuration helpers', () => { it( 'prefers the nested geo schema over legacy top-level keys', () => { const config = getGeoConfig( {