@owlmeans/web-panel 0.1.18-rc.72 → 0.1.18-rc.73

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 (38) hide show
  1. package/README.md +5 -3
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/web-panel/SKILL.md +28 -5
  4. package/build/auth/entrypoints.d.ts +1 -1
  5. package/build/auth/entrypoints.d.ts.map +1 -1
  6. package/build/components/login/types.d.ts +1 -1
  7. package/build/components/login/types.d.ts.map +1 -1
  8. package/build/consent/component.d.ts +4 -0
  9. package/build/consent/component.d.ts.map +1 -1
  10. package/build/consent/component.js +19 -5
  11. package/build/consent/component.js.map +1 -1
  12. package/build/consent/geo.d.ts +17 -0
  13. package/build/consent/geo.d.ts.map +1 -0
  14. package/build/consent/geo.js +19 -0
  15. package/build/consent/geo.js.map +1 -0
  16. package/build/consent/index.d.ts +4 -3
  17. package/build/consent/index.d.ts.map +1 -1
  18. package/build/consent/index.js +2 -1
  19. package/build/consent/index.js.map +1 -1
  20. package/build/consent/translate.js +1 -1
  21. package/build/consent/translate.js.map +1 -1
  22. package/build/consent/types.d.ts +20 -0
  23. package/build/consent/types.d.ts.map +1 -1
  24. package/build/entrypoints.d.ts +1 -7
  25. package/build/entrypoints.d.ts.map +1 -1
  26. package/build/types.d.ts +3 -0
  27. package/build/types.d.ts.map +1 -1
  28. package/package.json +23 -23
  29. package/src/components/login/types.ts +1 -1
  30. package/src/consent/component.tsx +30 -8
  31. package/src/consent/geo.ts +26 -0
  32. package/src/consent/index.ts +6 -2
  33. package/src/consent/translate.ts +1 -1
  34. package/src/consent/types.ts +22 -0
  35. package/src/types.ts +3 -0
  36. package/tests/consent.spec.ts +50 -0
  37. package/tests/harness/mount.tsx +48 -5
  38. package/tests/login.spec.ts +22 -0
package/README.md CHANGED
@@ -11,7 +11,7 @@ but never started.
11
11
  ## Installation
12
12
 
13
13
  ```sh
14
- bun add @owlmeans/web-panel@^0.1.18-rc.72
14
+ bun add @owlmeans/web-panel@^0.1.18-rc.73
15
15
  ```
16
16
 
17
17
  Peer requirements (the consuming app provides these): `react`, `react-dom`,
@@ -413,7 +413,9 @@ consumer's CSS puts dark tokens under `.dark` AND under
413
413
 
414
414
  | Symbol | Kind | Purpose |
415
415
  |---|---|---|
416
- | `PanelCookieConsent`, `PanelCookiePolicy` | component | `@owlmeans/web-consent` components bound to the app's language and translations |
416
+ | `PanelCookieConsent`, `PanelCookiePolicy` | component | `@owlmeans/web-consent` components bound to the app's language, translations and `cfg.cookieConsent` (`{ mode?, geo? }` — a prop wins) |
417
+ | `appendConsentGeoPlugin(ctx, plugin)` | function | Register the app's own country locator (`ConsentGeoPlugin`) while the context is configured; turns the geo gate on |
418
+ | `PanelCookieConsentConfig`, `ConsentGeoPluginAppend` | type | `cfg.cookieConsent`; the `context.consentGeo()` accessor |
417
419
  | `PanelConsentMenuWidget` | component | The cookie-preferences control for a host menu or footer ("Cookie settings" — pass `label` and `className`) |
418
420
  | `appendConsentWidgetService(ctx, alias?)`, `createConsentWidgetService` | function | Ref-counted presence service over a state resource |
419
421
  | `useConsentMenuPresence()`, `useConsentWidgetPresent()` | hook | Declare that a menu shows the row; read whether one does (hides the floating button) |
@@ -496,7 +498,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
496
498
  your project's skill store (`.agents/skills/`):
497
499
 
498
500
  ```sh
499
- npx @owlmeans/agent-skills@^0.1.18-rc.51
501
+ npx @owlmeans/agent-skills@^0.1.18-rc.52
500
502
  ```
501
503
 
502
504
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/web-panel",
4
- "version": "0.1.18-rc.72",
5
- "generatedAt": "2026-10-06T11:45:57.685Z",
4
+ "version": "0.1.18-rc.73",
5
+ "generatedAt": "2026-10-08T21:27:54.321Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -8,7 +8,7 @@ user-invocable: false
8
8
  # @owlmeans/web-panel
9
9
 
10
10
  **Layer:** Web (React)
11
- **Install:** `"@owlmeans/web-panel": "^0.1.18-rc.72"` in `dependencies`
11
+ **Install:** `"@owlmeans/web-panel": "^0.1.18-rc.73"` in `dependencies`
12
12
 
13
13
  ## Key Exports
14
14
 
@@ -481,8 +481,29 @@ landmark.
481
481
  ## Subpath: `./consent`
482
482
 
483
483
  `PanelCookieConsent` and `PanelCookiePolicy` — `@owlmeans/web-consent`'s components bound to this
484
- app's language and translations, falling through to the package's own seven-language bundle for
485
- every key the app has not overridden. See the `consent` skill.
484
+ app's language and translations, falling through to the package's own eight-language bundle for
485
+ every key the app has not overridden — and to the app's cookie-consent configuration. See the
486
+ `consent` and `web-consent` skills.
487
+
488
+ **The configuration lives in `cfg.cookieConsent`** (`PanelCookieConsentConfig`, on `AppConfig`),
489
+ set where the rest of the app's configuration is, before anything renders:
490
+
491
+ ```typescript
492
+ cfg.cookieConsent = { geo: { cloudflare: true } } // ask only in consent countries; mode: 'bar' default
493
+ ```
494
+
495
+ - `mode` (`'bar'` | `'window'`) and `geo` (`ConsentGeoOptions`) feed both components; a prop of the
496
+ same name always wins. Not `cfg.consent` — "consent" also names the marketing-consent and sign-in
497
+ terms options.
498
+ - A file restored into apps of every age (a generated target's `config.ts`) writes it as
499
+ `Object.assign(cfg, { cookieConsent: … })`, so a tree whose framework predates the key still
500
+ type-checks; an older package ignores it.
501
+ - **A locator of the app's own** is code, not configuration: `appendConsentGeoPlugin(context,
502
+ plugin)` in `context.ts` registers a `ConsentGeoPlugin` (`{ alias, locate }` — resolve the country
503
+ or throw) ahead of Cloudflare's and installs `context.consentGeo()`. Appending one IS asking for
504
+ the gate: `PanelCookieConsent` turns it on (`geo: {}`) even where `cfg.cookieConsent.geo` says
505
+ nothing. `PanelCookiePolicy` reads the same, so the policy states the regional rule exactly when
506
+ the dialog applies it.
486
507
 
487
508
  A re-export does not move Tailwind class strings, so a consumer adds a second `@source` for
488
509
  `@owlmeans/web-consent` alongside this package's — pointing at **`src`**, for the reason spelled out
@@ -532,7 +553,8 @@ menu has already mounted) and because React 18 StrictMode double-invokes mount/c
532
553
 
533
554
  | Job | Import from `@owlmeans/web-panel/consent` | Where it goes |
534
555
  |---|---|---|
535
- | The consent dialog (and its floating re-open button) | `PanelCookieConsent` | Beside the router — a `PanelApp` child — once |
556
+ | The consent UI — bar, window, spinner (and its floating re-open button) | `PanelCookieConsent` | Beside the router — a `PanelApp` child — once |
557
+ | Asking only in consent countries | `cfg.cookieConsent.geo`, or `appendConsentGeoPlugin(context, plugin)` | The app's `config.ts` / `context.ts` |
536
558
  | The "Cookie settings" control in a footer or menu | `PanelConsentMenuWidget` (`label`, `className`, `onSelect?` — defaults to `consentStore.open('reopen')`) | Inside the host's own footer/menu |
537
559
  | Hiding the floating button while that control is reachable | `useConsentMenuPresence()` | Called by the always-mounted component that renders the control — mounted ONLY while the control can actually be reached: a component that is merely hidden by CSS (`xl:hidden`) is still mounted and still claims presence, so the floating button vanishes with nothing else to open the dialog; render it conditionally instead (viable's header mounts its hamburger menu only below the full-row breakpoint) |
538
560
  | Registering the presence service | `appendConsentWidgetService(context)` | The app's `context.ts` |
@@ -557,7 +579,8 @@ bound dialog with its floating button; `useConsentMenuPresence()` then has nothi
557
579
  does nothing. The presence read itself (`useConsentWidgetPresent`) still needs the service — its
558
580
  state resource is registered by it — so any other caller checks first. A dialog that read an
559
581
  unregistered state resource threw inside render ("Resource consent-widget-presence not found") and
560
- blanked the whole application. Pinned by `tests/consent.spec.ts` (`?consent=bare` / `?consent=menu`).
582
+ blanked the whole application. Pinned by `tests/consent.spec.ts` (`?consent=bare` / `?consent=menu`;
583
+ the configuration by `?consentCfg=window|bar`, `?modeProp=` and `?consentGeo=<CC>|fail`).
561
584
 
562
585
  ## Consumer setup — package boundary and Tailwind
563
586
 
@@ -6,5 +6,5 @@ export declare const entrypoints: (import("@owlmeans/client-entrypoint").ClientP
6
6
  query: Partial<import("@owlmeans/auth").AuthToken>;
7
7
  }, undefined>> | import("@owlmeans/client-entrypoint").ClientProtocolEntrypoint<import("@owlmeans/entrypoint").EntrypointProtocol<{
8
8
  query: import("@owlmeans/auth").AuthToken;
9
- }, undefined>>)[];
9
+ }, unknown>>)[];
10
10
  //# sourceMappingURL=entrypoints.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"entrypoints.d.ts","sourceRoot":"","sources":["../../src/auth/entrypoints.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW;;;;;;;;iBAAuC,CAAA"}
1
+ {"version":3,"file":"entrypoints.d.ts","sourceRoot":"","sources":["../../src/auth/entrypoints.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW;;;;;;;;eAAuC,CAAA"}
@@ -15,7 +15,7 @@ export interface LoginCreditProps {
15
15
  export interface LoginTermsProps {
16
16
  model: LoginTermsModel;
17
17
  translate: (key: string, defaultValue: string) => string;
18
- /** The current language, for `Intl.ListFormat` and a document's own locale-keyed label. */
18
+ /** The current language, for `Intl.ListFormat`, document labels and explicit translated hrefs. */
19
19
  locale?: string;
20
20
  className?: string;
21
21
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/login/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAExG,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,gBAAgB,EAAE,WAAW,CAAC;IAC3E,0DAA0D;IAC1D,IAAI,CAAC,EAAE,aAAa,CAAC;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,SAAS,CAAA;CACzD;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,gBAAgB,CAAA;IACvB,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,MAAM,CAAA;IACxD,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,eAAe,CAAA;IACtB,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,MAAM,CAAA;IACxD,2FAA2F;IAC3F,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,MAAM,uBAAuB,GAAG,eAAe,CAAA;AAErD,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,kBAAkB,CAAA;IACzB,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,MAAM,CAAA;IACxD,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/login/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAExG,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,gBAAgB,EAAE,WAAW,CAAC;IAC3E,0DAA0D;IAC1D,IAAI,CAAC,EAAE,aAAa,CAAC;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,SAAS,CAAA;CACzD;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,gBAAgB,CAAA;IACvB,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,MAAM,CAAA;IACxD,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,eAAe,CAAA;IACtB,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,MAAM,CAAA;IACxD,kGAAkG;IAClG,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,MAAM,uBAAuB,GAAG,eAAe,CAAA;AAErD,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,kBAAkB,CAAA;IACzB,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,MAAM,CAAA;IACxD,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB"}
@@ -19,5 +19,9 @@ import { type CookieConsentProps, type CookiePolicyProps } from '@owlmeans/web-c
19
19
  * while the context is built, before anything renders.
20
20
  */
21
21
  export declare const PanelCookieConsent: FC<CookieConsentProps>;
22
+ /**
23
+ * The cookie-policy page, bound like the dialog — and told about the geo gate the same way, so it
24
+ * states the regional rule exactly when the dialog applies it.
25
+ */
22
26
  export declare const PanelCookiePolicy: FC<CookiePolicyProps>;
23
27
  //# sourceMappingURL=component.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../src/consent/component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAC/B,OAAO,EAA+B,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AA0BpH;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,kBAAkB,EAAE,EAAE,CAAC,kBAAkB,CAMrD,CAAA;AAED,eAAO,MAAM,iBAAiB,EAAE,EAAE,CAAC,iBAAiB,CAMnD,CAAA"}
1
+ {"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../src/consent/component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAC/B,OAAO,EACgD,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EACrG,MAAM,uBAAuB,CAAA;AAqC9B;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,kBAAkB,EAAE,EAAE,CAAC,kBAAkB,CAWrD,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,EAAE,CAAC,iBAAiB,CAQnD,CAAA"}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { CookieConsent, CookiePolicy } from '@owlmeans/web-consent';
2
+ import { CookieConsent, CookiePolicy, } from '@owlmeans/web-consent';
3
3
  import { useLanguage } from '@owlmeans/client-i18n';
4
4
  import { useContext } from '../context.js';
5
5
  import { useConsentTranslate } from './translate.js';
@@ -12,6 +12,12 @@ const BoundCookieConsent = ({ menuPresent, ...props }) => {
12
12
  return _jsx(CookieConsent, { ...props, locale: locale, translate: translate, noReopenButton: props.noReopenButton ?? menuPresent });
13
13
  };
14
14
  const MenuAwareCookieConsent = props => _jsx(BoundCookieConsent, { ...props, menuPresent: useConsentWidgetPresent() });
15
+ /**
16
+ * The geo gate in force: the prop, else `cfg.cookieConsent.geo`, else — when the application
17
+ * appended a locator of its own — the gate with nothing but that locator.
18
+ */
19
+ const geoOf = (prop, context) => prop ?? context.cfg.cookieConsent?.geo ?? (typeof context.consentGeo === 'function' ? {} : undefined);
20
+ void _localeParity;
15
21
  /**
16
22
  * The consent dialog, bound to this application's language and translations.
17
23
  *
@@ -32,15 +38,23 @@ const MenuAwareCookieConsent = props => _jsx(BoundCookieConsent, { ...props, men
32
38
  */
33
39
  export const PanelCookieConsent = props => {
34
40
  const context = useContext();
41
+ const mode = props.mode ?? context.cfg.cookieConsent?.mode;
42
+ const geo = geoOf(props.geo, context);
43
+ const bound = { ...props, ...(mode != null ? { mode } : {}), ...(geo != null ? { geo } : {}) };
35
44
  return typeof context.consentWidget === 'function'
36
- ? _jsx(MenuAwareCookieConsent, { ...props })
37
- : _jsx(BoundCookieConsent, { ...props });
45
+ ? _jsx(MenuAwareCookieConsent, { ...bound })
46
+ : _jsx(BoundCookieConsent, { ...bound });
38
47
  };
48
+ /**
49
+ * The cookie-policy page, bound like the dialog — and told about the geo gate the same way, so it
50
+ * states the regional rule exactly when the dialog applies it.
51
+ */
39
52
  export const PanelCookiePolicy = props => {
53
+ const context = useContext();
40
54
  const [lng] = useLanguage();
41
55
  const locale = props.locale ?? lng;
42
56
  const translate = useConsentTranslate(locale, props.translate);
43
- return _jsx(CookiePolicy, { ...props, locale: locale, translate: translate });
57
+ const geo = geoOf(props.geo, context);
58
+ return _jsx(CookiePolicy, { ...props, ...(geo != null ? { geo } : {}), locale: locale, translate: translate });
44
59
  };
45
- void _localeParity;
46
60
  //# sourceMappingURL=component.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"component.js","sourceRoot":"","sources":["../../src/consent/component.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,aAAa,EAAE,YAAY,EAAmD,MAAM,uBAAuB,CAAA;AACpH,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAE1C,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AACpD,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAA;AAEvD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAGjD,MAAM,kBAAkB,GAAgC,CAAC,EAAE,WAAW,EAAE,GAAG,KAAK,EAAE,EAAE,EAAE;IACpF,MAAM,CAAC,GAAG,CAAC,GAAG,WAAW,EAAE,CAAA;IAC3B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,GAAG,CAAA;IAClC,MAAM,SAAS,GAAG,mBAAmB,CAAC,MAAM,EAAE,KAAK,CAAC,SAAS,CAAC,CAAA;IAE9D,OAAO,KAAC,aAAa,OACf,KAAK,EACT,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,KAAK,CAAC,cAAc,IAAI,WAAW,GACnD,CAAA;AACJ,CAAC,CAAA;AAED,MAAM,sBAAsB,GAA2B,KAAK,CAAC,EAAE,CAC7D,KAAC,kBAAkB,OAAK,KAAK,EAAE,WAAW,EAAE,uBAAuB,EAAE,GAAI,CAAA;AAE3E;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAA2B,KAAK,CAAC,EAAE;IAChE,MAAM,OAAO,GAAG,UAAU,EAA0E,CAAA;IAEpG,OAAO,OAAO,OAAO,CAAC,aAAa,KAAK,UAAU;QAChD,CAAC,CAAC,KAAC,sBAAsB,OAAK,KAAK,GAAI;QACvC,CAAC,CAAC,KAAC,kBAAkB,OAAK,KAAK,GAAI,CAAA;AACvC,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,iBAAiB,GAA0B,KAAK,CAAC,EAAE;IAC9D,MAAM,CAAC,GAAG,CAAC,GAAG,WAAW,EAAE,CAAA;IAC3B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,GAAG,CAAA;IAClC,MAAM,SAAS,GAAG,mBAAmB,CAAC,MAAM,EAAE,KAAK,CAAC,SAAS,CAAC,CAAA;IAE9D,OAAO,KAAC,YAAY,OAAK,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAI,CAAA;AAC1E,CAAC,CAAA;AAED,KAAK,aAAa,CAAA"}
1
+ {"version":3,"file":"component.js","sourceRoot":"","sources":["../../src/consent/component.tsx"],"names":[],"mappings":";AACA,OAAO,EACL,aAAa,EAAE,YAAY,GAC5B,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAE1C,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AACpD,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAA;AAEvD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAGjD,MAAM,kBAAkB,GAAgC,CAAC,EAAE,WAAW,EAAE,GAAG,KAAK,EAAE,EAAE,EAAE;IACpF,MAAM,CAAC,GAAG,CAAC,GAAG,WAAW,EAAE,CAAA;IAC3B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,GAAG,CAAA;IAClC,MAAM,SAAS,GAAG,mBAAmB,CAAC,MAAM,EAAE,KAAK,CAAC,SAAS,CAAC,CAAA;IAE9D,OAAO,KAAC,aAAa,OACf,KAAK,EACT,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,KAAK,CAAC,cAAc,IAAI,WAAW,GACnD,CAAA;AACJ,CAAC,CAAA;AAED,MAAM,sBAAsB,GAA2B,KAAK,CAAC,EAAE,CAC7D,KAAC,kBAAkB,OAAK,KAAK,EAAE,WAAW,EAAE,uBAAuB,EAAE,GAAI,CAAA;AAE3E;;;GAGG;AACH,MAAM,KAAK,GAAG,CACZ,IAAmC,EAAE,OAAgE,EACtE,EAAE,CACjC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,GAAG,IAAI,CAAC,OAAO,OAAO,CAAC,UAAU,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;AAEvG,KAAK,aAAa,CAAA;AAElB;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAA2B,KAAK,CAAC,EAAE;IAChE,MAAM,OAAO,GAAG,UAAU,EAEvB,CAAA;IACH,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,CAAA;IAC1D,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IACrC,MAAM,KAAK,GAAG,EAAE,GAAG,KAAK,EAAE,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;IAE9F,OAAO,OAAO,OAAO,CAAC,aAAa,KAAK,UAAU;QAChD,CAAC,CAAC,KAAC,sBAAsB,OAAK,KAAK,GAAI;QACvC,CAAC,CAAC,KAAC,kBAAkB,OAAK,KAAK,GAAI,CAAA;AACvC,CAAC,CAAA;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAA0B,KAAK,CAAC,EAAE;IAC9D,MAAM,OAAO,GAAG,UAAU,EAAsE,CAAA;IAChG,MAAM,CAAC,GAAG,CAAC,GAAG,WAAW,EAAE,CAAA;IAC3B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,GAAG,CAAA;IAClC,MAAM,SAAS,GAAG,mBAAmB,CAAC,MAAM,EAAE,KAAK,CAAC,SAAS,CAAC,CAAA;IAC9D,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IAErC,OAAO,KAAC,YAAY,OAAK,KAAK,KAAM,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAI,CAAA;AAC5G,CAAC,CAAA"}
@@ -0,0 +1,17 @@
1
+ import { type ConsentGeoPlugin } from '@owlmeans/web-consent';
2
+ import type { ClientConfig } from '@owlmeans/client-context';
3
+ import type { ClientContext } from '@owlmeans/client';
4
+ import type { ConsentGeoPluginAppend } from './types.js';
5
+ /**
6
+ * Add the application's own country locator while its context is configured — beside
7
+ * `appendConsentWidgetService`, before anything renders.
8
+ *
9
+ * A locator is one async method that resolves the visitor's country or throws
10
+ * (`ConsentGeoPlugin`). It is registered into `@owlmeans/consent`'s plugin registry, ahead of the
11
+ * built-in Cloudflare one, and `PanelCookieConsent` turns the geo gate on for it even where
12
+ * `cfg.cookieConsent.geo` says nothing — appending a locator IS asking for the gate. Its result
13
+ * decides as Cloudflare's would: a visitor in a consent country is asked, anyone else is decided for
14
+ * automatically, and a throw hands over to the next locator — with none left, the visitor is asked.
15
+ */
16
+ export declare const appendConsentGeoPlugin: <C extends ClientConfig, T extends ClientContext<C>>(ctx: T, plugin: ConsentGeoPlugin) => T & ConsentGeoPluginAppend;
17
+ //# sourceMappingURL=geo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geo.d.ts","sourceRoot":"","sources":["../../src/consent/geo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAClF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAExD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,GAAI,CAAC,SAAS,YAAY,EAAE,CAAC,SAAS,aAAa,CAAC,CAAC,CAAC,OAClF,CAAC,UAAU,gBAAgB,KAC/B,CAAC,GAAG,sBAON,CAAA"}
@@ -0,0 +1,19 @@
1
+ import { consentPluginHelper } from '@owlmeans/web-consent';
2
+ /**
3
+ * Add the application's own country locator while its context is configured — beside
4
+ * `appendConsentWidgetService`, before anything renders.
5
+ *
6
+ * A locator is one async method that resolves the visitor's country or throws
7
+ * (`ConsentGeoPlugin`). It is registered into `@owlmeans/consent`'s plugin registry, ahead of the
8
+ * built-in Cloudflare one, and `PanelCookieConsent` turns the geo gate on for it even where
9
+ * `cfg.cookieConsent.geo` says nothing — appending a locator IS asking for the gate. Its result
10
+ * decides as Cloudflare's would: a visitor in a consent country is asked, anyone else is decided for
11
+ * automatically, and a throw hands over to the next locator — with none left, the visitor is asked.
12
+ */
13
+ export const appendConsentGeoPlugin = (ctx, plugin) => {
14
+ const _ctx = ctx;
15
+ consentPluginHelper.registerConsentPlugin(plugin);
16
+ _ctx.consentGeo = () => plugin;
17
+ return _ctx;
18
+ };
19
+ //# sourceMappingURL=geo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geo.js","sourceRoot":"","sources":["../../src/consent/geo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAyB,MAAM,uBAAuB,CAAA;AAKlF;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CACpC,GAAM,EAAE,MAAwB,EACJ,EAAE;IAC9B,MAAM,IAAI,GAAG,GAAiC,CAAA;IAE9C,mBAAmB,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAA;IACjD,IAAI,CAAC,UAAU,GAAG,GAAG,EAAE,CAAC,MAAM,CAAA;IAE9B,OAAO,IAAI,CAAA;AACb,CAAC,CAAA"}
@@ -3,8 +3,9 @@ export * from './menu-widget.js';
3
3
  export * from './presence.js';
4
4
  export { installConsentLanguage } from './language.js';
5
5
  export { createConsentWidgetService, appendConsentWidgetService, CONSENT_WIDGET_STATE } from './service.js';
6
+ export { appendConsentGeoPlugin } from './geo.js';
6
7
  export { CONSENT_WIDGET_SERVICE } from './consts.js';
7
- export type { ConsentWidgetPresenceRecord, ConsentWidgetService, ConsentWidgetServiceAppend } from './types.js';
8
- export { useConsent, useConsentCategory, consentStore, openConsent, isConsented, readConsent, writeConsent, clearConsent, DEFAULT_CONSENT_CATEGORIES, CONSENT_KEY, CONSENT_ESSENTIAL, CONSENT_ANALYTICS, CONSENT_MARKETING, CONSENT_EVENT, consentBootstrapScript, } from '@owlmeans/web-consent';
9
- export type { ConsentCategory, ConsentMenuWidgetProps, ConsentOptions, ConsentRecord, ConsentSignal, ConsentState, CookieConsentProps, CookiePolicyProps, } from '@owlmeans/web-consent';
8
+ export type { ConsentWidgetPresenceRecord, ConsentWidgetService, ConsentWidgetServiceAppend, PanelCookieConsentConfig, ConsentGeoPluginAppend, } from './types.js';
9
+ export { useConsent, useConsentCategory, consentStore, openConsent, isConsented, readConsent, writeConsent, clearConsent, DEFAULT_CONSENT_CATEGORIES, CONSENT_KEY, CONSENT_ESSENTIAL, CONSENT_ANALYTICS, CONSENT_MARKETING, CONSENT_EVENT, consentBootstrapScript, CONSENT_REQUIRED_COUNTRIES, } from '@owlmeans/web-consent';
10
+ export type { ConsentCategory, ConsentMenuWidgetProps, ConsentOptions, ConsentRecord, ConsentSignal, ConsentState, CookieConsentProps, CookiePolicyProps, ConsentDisplayMode, ConsentGeoOptions, ConsentGeoPlugin, ConsentGeoLocation, } from '@owlmeans/web-consent';
10
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/consent/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,eAAe,CAAA;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAA;AACtD,OAAO,EAAE,0BAA0B,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAC3G,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AACpD,YAAY,EACV,2BAA2B,EAAE,oBAAoB,EAAE,0BAA0B,EAC9E,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,UAAU,EAAE,kBAAkB,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EACtE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,0BAA0B,EACnE,WAAW,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,EACpE,aAAa,EACb,sBAAsB,GACvB,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EACV,eAAe,EAAE,sBAAsB,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EACrF,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,GACpD,MAAM,uBAAuB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/consent/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,eAAe,CAAA;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAA;AACtD,OAAO,EAAE,0BAA0B,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAC3G,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AACpD,YAAY,EACV,2BAA2B,EAAE,oBAAoB,EAAE,0BAA0B,EAC7E,wBAAwB,EAAE,sBAAsB,GACjD,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,UAAU,EAAE,kBAAkB,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EACtE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,0BAA0B,EACnE,WAAW,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,EACpE,aAAa,EACb,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EACV,eAAe,EAAE,sBAAsB,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EACrF,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,iBAAiB,EAC1F,gBAAgB,EAAE,kBAAkB,GACrC,MAAM,uBAAuB,CAAA"}
@@ -3,6 +3,7 @@ export * from './menu-widget.js';
3
3
  export * from './presence.js';
4
4
  export { installConsentLanguage } from './language.js';
5
5
  export { createConsentWidgetService, appendConsentWidgetService, CONSENT_WIDGET_STATE } from './service.js';
6
+ export { appendConsentGeoPlugin } from './geo.js';
6
7
  export { CONSENT_WIDGET_SERVICE } from './consts.js';
7
- export { useConsent, useConsentCategory, consentStore, openConsent, isConsented, readConsent, writeConsent, clearConsent, DEFAULT_CONSENT_CATEGORIES, CONSENT_KEY, CONSENT_ESSENTIAL, CONSENT_ANALYTICS, CONSENT_MARKETING, CONSENT_EVENT, consentBootstrapScript, } from '@owlmeans/web-consent';
8
+ export { useConsent, useConsentCategory, consentStore, openConsent, isConsented, readConsent, writeConsent, clearConsent, DEFAULT_CONSENT_CATEGORIES, CONSENT_KEY, CONSENT_ESSENTIAL, CONSENT_ANALYTICS, CONSENT_MARKETING, CONSENT_EVENT, consentBootstrapScript, CONSENT_REQUIRED_COUNTRIES, } from '@owlmeans/web-consent';
8
9
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/consent/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,eAAe,CAAA;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAA;AACtD,OAAO,EAAE,0BAA0B,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAC3G,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAIpD,OAAO,EACL,UAAU,EAAE,kBAAkB,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EACtE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,0BAA0B,EACnE,WAAW,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,EACpE,aAAa,EACb,sBAAsB,GACvB,MAAM,uBAAuB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/consent/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,eAAe,CAAA;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAA;AACtD,OAAO,EAAE,0BAA0B,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAC3G,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAKpD,OAAO,EACL,UAAU,EAAE,kBAAkB,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EACtE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,0BAA0B,EACnE,WAAW,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,EACpE,aAAa,EACb,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,uBAAuB,CAAA"}
@@ -1,6 +1,6 @@
1
1
  import { useCallback } from 'react';
2
2
  import { useI18nLib } from '@owlmeans/client-i18n';
3
- import { consentI18nHelper } from '@owlmeans/consent';
3
+ import { consentI18nHelper } from '@owlmeans/web-consent';
4
4
  /**
5
5
  * Resolve consent copy through the application first, and the packaged bundle second.
6
6
  *
@@ -1 +1 @@
1
- {"version":3,"file":"translate.js","sourceRoot":"","sources":["../../src/consent/translate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAErD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,MAAc,EAAE,QAAwD,EACvB,EAAE;IACnD,MAAM,CAAC,GAAG,UAAU,CAAC,SAAS,CAAC,CAAA;IAE/B,OAAO,WAAW,CAAC,CAAC,GAAW,EAAE,YAAoB,EAAE,EAAE;QACvD,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;YACrB,OAAO,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAC,CAAA;QACpC,CAAC;QACD,MAAM,QAAQ,GAAG,iBAAiB,CAAC,uBAAuB,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,YAAY,CAAC,CAAA;QACrF,2FAA2F;QAC3F,2FAA2F;QAC3F,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC,CAAA;IAC3C,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAA;AAC3B,CAAC,CAAA"}
1
+ {"version":3,"file":"translate.js","sourceRoot":"","sources":["../../src/consent/translate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAEzD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,MAAc,EAAE,QAAwD,EACvB,EAAE;IACnD,MAAM,CAAC,GAAG,UAAU,CAAC,SAAS,CAAC,CAAA;IAE/B,OAAO,WAAW,CAAC,CAAC,GAAW,EAAE,YAAoB,EAAE,EAAE;QACvD,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;YACrB,OAAO,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAC,CAAA;QACpC,CAAC;QACD,MAAM,QAAQ,GAAG,iBAAiB,CAAC,uBAAuB,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,YAAY,CAAC,CAAA;QACrF,2FAA2F;QAC3F,2FAA2F;QAC3F,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC,CAAA;IAC3C,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAA;AAC3B,CAAC,CAAA"}
@@ -1,5 +1,25 @@
1
1
  import type { ResourceRecord } from '@owlmeans/resource';
2
2
  import type { InitializedService } from '@owlmeans/context';
3
+ import type { ConsentDisplayMode, ConsentGeoOptions, ConsentGeoPlugin } from '@owlmeans/web-consent';
4
+ /**
5
+ * `cfg.cookieConsent` — how the application's cookie consent asks, set where the rest of its
6
+ * configuration is. A prop on `PanelCookieConsent` / `PanelCookiePolicy` always wins.
7
+ */
8
+ export interface PanelCookieConsentConfig {
9
+ /** `bar` (the default) or `window` — see `ConsentDisplayMode`. */
10
+ mode?: ConsentDisplayMode;
11
+ /**
12
+ * Ask only where the law requires it. `{ cloudflare: true }` locates the visitor through the
13
+ * Cloudflare edge the application is served from; a locator of the application's own is added in
14
+ * code with `appendConsentGeoPlugin`. See `ConsentGeoOptions`.
15
+ */
16
+ geo?: ConsentGeoOptions;
17
+ }
18
+ /** What `appendConsentGeoPlugin` adds to the context. */
19
+ export interface ConsentGeoPluginAppend {
20
+ /** The application's own locator, registered while the context was configured. */
21
+ consentGeo: () => ConsentGeoPlugin;
22
+ }
3
23
  /** A `single`-store presence flag: one record, no id needed. */
4
24
  export interface ConsentWidgetPresenceRecord extends ResourceRecord {
5
25
  present: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/consent/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAE3D,gEAAgE;AAChE,MAAM,WAAW,2BAA4B,SAAQ,cAAc;IACjE,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,oBAAqB,SAAQ,kBAAkB;IAC9D;;;OAGG;IACH,KAAK,EAAE,MAAM,MAAM,IAAI,CAAA;IACvB,yEAAyE;IACzE,OAAO,EAAE,MAAM,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,0BAA0B;IACzC,aAAa,EAAE,MAAM,oBAAoB,CAAA;CAC1C"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/consent/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAEpG;;;GAGG;AACH,MAAM,WAAW,wBAAwB;IACvC,kEAAkE;IAClE,IAAI,CAAC,EAAE,kBAAkB,CAAA;IACzB;;;;OAIG;IACH,GAAG,CAAC,EAAE,iBAAiB,CAAA;CACxB;AAED,yDAAyD;AACzD,MAAM,WAAW,sBAAsB;IACrC,kFAAkF;IAClF,UAAU,EAAE,MAAM,gBAAgB,CAAA;CACnC;AAED,gEAAgE;AAChE,MAAM,WAAW,2BAA4B,SAAQ,cAAc;IACjE,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,oBAAqB,SAAQ,kBAAkB;IAC9D;;;OAGG;IACH,KAAK,EAAE,MAAM,MAAM,IAAI,CAAA;IACvB,yEAAyE;IACzE,OAAO,EAAE,MAAM,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,0BAA0B;IACzC,aAAa,EAAE,MAAM,oBAAoB,CAAA;CAC1C"}
@@ -1,8 +1,2 @@
1
- export declare const entrypoints: (import("@owlmeans/client-entrypoint").ClientProtocolEntrypoint<import("@owlmeans/entrypoint").EntrypointProtocol<import("@owlmeans/entrypoint").OpenRequest, import("@owlmeans/entrypoint").OpenValue>> | import("@owlmeans/client-entrypoint").ClientProtocolEntrypoint<import("@owlmeans/entrypoint").EntrypointProtocol<{
2
- query: import("@owlmeans/auth").AuthToken;
3
- }, unknown>> | import("@owlmeans/client-entrypoint").ClientProtocolEntrypoint<import("@owlmeans/entrypoint").EntrypointProtocol<{
4
- query: import("@owlmeans/auth-common").SurrogateQuery;
5
- }, unknown>> | import("@owlmeans/client-entrypoint").ClientProtocolEntrypoint<import("@owlmeans/entrypoint").EntrypointProtocol<{
6
- body: import("@owlmeans/auth").AuthToken;
7
- }, import("@owlmeans/auth").AuthToken>>)[];
1
+ export declare const entrypoints: import("@owlmeans/client-entrypoint").ClientProtocolEntrypoint<any>[];
8
2
  //# sourceMappingURL=entrypoints.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"entrypoints.d.ts","sourceRoot":"","sources":["../src/entrypoints.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW;;;;;;0CAA4C,CAAA"}
1
+ {"version":3,"file":"entrypoints.d.ts","sourceRoot":"","sources":["../src/entrypoints.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW,uEAA4C,CAAA"}
package/build/types.d.ts CHANGED
@@ -2,6 +2,7 @@ import type { WithFlowConfig } from '@owlmeans/flow';
2
2
  import type { AppConfig as Config, AppContext as Context, RenderOptions } from '@owlmeans/web-client';
3
3
  import type { FlowService } from '@owlmeans/web-flow';
4
4
  import type { SocketClientSettings, SocketStatusServiceAppend } from '@owlmeans/client-socket';
5
+ import type { PanelCookieConsentConfig } from './consent/types.js';
5
6
  export interface AppConfig extends Config, WithFlowConfig {
6
7
  socket?: SocketClientSettings & {
7
8
  /**
@@ -12,6 +13,8 @@ export interface AppConfig extends Config, WithFlowConfig {
12
13
  */
13
14
  reloadDialog?: boolean;
14
15
  };
16
+ /** How the cookie consent asks — the bar or the window, and where (`geo`). See `./consent`. */
17
+ cookieConsent?: PanelCookieConsentConfig;
15
18
  }
16
19
  export interface AppContext<C extends AppConfig = AppConfig> extends Context<C>, SocketStatusServiceAppend {
17
20
  flow: () => FlowService;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACpD,OAAO,KAAK,EAAE,SAAS,IAAI,MAAM,EAAE,UAAU,IAAI,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACrG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,KAAK,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAA;AAE9F,MAAM,WAAW,SAAU,SAAQ,MAAM,EAAE,cAAc;IACvD,MAAM,CAAC,EAAE,oBAAoB,GAAG;QAC9B;;;;;WAKG;QACH,YAAY,CAAC,EAAE,OAAO,CAAA;KACvB,CAAA;CACF;AAED,MAAM,WAAW,UAAU,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS,CAAE,SAAQ,OAAO,CAAC,CAAC,CAAC,EAAE,yBAAyB;IACxG,IAAI,EAAE,MAAM,WAAW,CAAA;CACxB;AAED,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACrD,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACpD,OAAO,KAAK,EAAE,SAAS,IAAI,MAAM,EAAE,UAAU,IAAI,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACrG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,KAAK,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAA;AAC9F,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAA;AAElE,MAAM,WAAW,SAAU,SAAQ,MAAM,EAAE,cAAc;IACvD,MAAM,CAAC,EAAE,oBAAoB,GAAG;QAC9B;;;;;WAKG;QACH,YAAY,CAAC,EAAE,OAAO,CAAA;KACvB,CAAA;IACD,+FAA+F;IAC/F,aAAa,CAAC,EAAE,wBAAwB,CAAA;CACzC;AAED,MAAM,WAAW,UAAU,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS,CAAE,SAAQ,OAAO,CAAC,CAAC,CAAC,EAAE,yBAAyB;IACxG,IAAI,EAAE,MAAM,WAAW,CAAA;CACxB;AAED,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACrD,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/web-panel",
3
- "version": "0.1.18-rc.72",
3
+ "version": "0.1.18-rc.73",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -51,35 +51,35 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "@hookform/resolvers": "^5.9.1",
54
- "@owlmeans/api-config-client": "^0.1.18-rc.46",
55
- "@owlmeans/auth": "^0.1.18-rc.40",
56
- "@owlmeans/auth-common": "^0.1.18-rc.48",
57
- "@owlmeans/basic-envelope": "^0.1.18-rc.47",
58
- "@owlmeans/client": "^0.1.18-rc.52",
59
- "@owlmeans/client-auth": "^0.1.18-rc.59",
60
- "@owlmeans/client-config": "^0.1.18-rc.44",
61
- "@owlmeans/client-context": "^0.1.18-rc.49",
62
- "@owlmeans/client-entrypoint": "^0.1.18-rc.48",
63
- "@owlmeans/api": "^0.1.18-rc.48",
64
- "@owlmeans/client-flow": "^0.1.18-rc.55",
65
- "@owlmeans/client-i18n": "^0.1.18-rc.51",
66
- "@owlmeans/client-panel": "^0.1.18-rc.59",
67
- "@owlmeans/client-route": "^0.1.18-rc.49",
68
- "@owlmeans/client-socket": "^0.1.18-rc.50",
54
+ "@owlmeans/api-config-client": "^0.1.18-rc.47",
55
+ "@owlmeans/auth": "^0.1.18-rc.41",
56
+ "@owlmeans/auth-common": "^0.1.18-rc.49",
57
+ "@owlmeans/basic-envelope": "^0.1.18-rc.48",
58
+ "@owlmeans/client": "^0.1.18-rc.53",
59
+ "@owlmeans/client-auth": "^0.1.18-rc.60",
60
+ "@owlmeans/client-config": "^0.1.18-rc.45",
61
+ "@owlmeans/client-context": "^0.1.18-rc.50",
62
+ "@owlmeans/client-entrypoint": "^0.1.18-rc.49",
63
+ "@owlmeans/api": "^0.1.18-rc.49",
64
+ "@owlmeans/client-flow": "^0.1.18-rc.56",
65
+ "@owlmeans/client-i18n": "^0.1.18-rc.52",
66
+ "@owlmeans/client-panel": "^0.1.18-rc.60",
67
+ "@owlmeans/client-route": "^0.1.18-rc.50",
68
+ "@owlmeans/client-socket": "^0.1.18-rc.51",
69
69
  "@owlmeans/config": "^0.1.18-rc.44",
70
70
  "@owlmeans/context": "^0.1.18-rc.34",
71
71
  "@owlmeans/entrypoint": "^0.1.18-rc.41",
72
72
  "@owlmeans/error": "^0.1.18-rc.38",
73
73
  "@owlmeans/flow": "^0.1.18-rc.45",
74
74
  "@owlmeans/i18n": "^0.1.18-rc.39",
75
- "@owlmeans/log": "^0.1.18-rc.2",
75
+ "@owlmeans/log": "^0.1.18-rc.3",
76
76
  "@owlmeans/route": "^0.1.18-rc.35",
77
77
  "@owlmeans/state": "^0.1.18-rc.39",
78
- "@owlmeans/web-client": "^0.1.18-rc.62",
79
- "@owlmeans/web-consent": "^0.1.18-rc.43",
80
- "@owlmeans/web-db": "^0.1.18-rc.49",
81
- "@owlmeans/web-flow": "^0.1.18-rc.54",
82
- "@owlmeans/web-router": "^0.1.18-rc.50",
78
+ "@owlmeans/web-client": "^0.1.18-rc.63",
79
+ "@owlmeans/web-consent": "^0.1.18-rc.44",
80
+ "@owlmeans/web-db": "^0.1.18-rc.50",
81
+ "@owlmeans/web-flow": "^0.1.18-rc.55",
82
+ "@owlmeans/web-router": "^0.1.18-rc.51",
83
83
  "i18next-browser-languagedetector": "^8.2.1",
84
84
  "qrcode.react": "^4.2.0",
85
85
  "react-google-recaptcha": "^3.1.0",
@@ -105,7 +105,7 @@
105
105
  },
106
106
  "devDependencies": {
107
107
  "@owlmeans/dep-config": "workspace:*",
108
- "@owlmeans/test-ui": "^0.1.18-rc.50",
108
+ "@owlmeans/test-ui": "^0.1.18-rc.51",
109
109
  "@radix-ui/react-alert-dialog": "^1.1.23",
110
110
  "@radix-ui/react-dialog": "^1.1.23",
111
111
  "@radix-ui/react-label": "^2.1.15",
@@ -16,7 +16,7 @@ export interface LoginCreditProps {
16
16
  export interface LoginTermsProps {
17
17
  model: LoginTermsModel
18
18
  translate: (key: string, defaultValue: string) => string
19
- /** The current language, for `Intl.ListFormat` and a document's own locale-keyed label. */
19
+ /** The current language, for `Intl.ListFormat`, document labels and explicit translated hrefs. */
20
20
  locale?: string
21
21
  className?: string
22
22
  }
@@ -1,11 +1,13 @@
1
1
  import type { FC } from 'react'
2
- import { CookieConsent, CookiePolicy, type CookieConsentProps, type CookiePolicyProps } from '@owlmeans/web-consent'
2
+ import {
3
+ CookieConsent, CookiePolicy, type ConsentGeoOptions, type CookieConsentProps, type CookiePolicyProps,
4
+ } from '@owlmeans/web-consent'
3
5
  import { useLanguage } from '@owlmeans/client-i18n'
4
6
  import { useContext } from '../context.js'
5
7
  import type { AppConfig, AppContext } from '../types.js'
6
8
  import { useConsentTranslate } from './translate.js'
7
9
  import { useConsentWidgetPresent } from './presence.js'
8
- import type { ConsentWidgetServiceAppend } from './types.js'
10
+ import type { ConsentGeoPluginAppend, ConsentWidgetServiceAppend } from './types.js'
9
11
  import { _localeParity } from './consts.local.js'
10
12
  import type { BoundCookieConsentProps } from './types.local.js'
11
13
 
@@ -25,6 +27,17 @@ const BoundCookieConsent: FC<BoundCookieConsentProps> = ({ menuPresent, ...props
25
27
  const MenuAwareCookieConsent: FC<CookieConsentProps> = props =>
26
28
  <BoundCookieConsent {...props} menuPresent={useConsentWidgetPresent()} />
27
29
 
30
+ /**
31
+ * The geo gate in force: the prop, else `cfg.cookieConsent.geo`, else — when the application
32
+ * appended a locator of its own — the gate with nothing but that locator.
33
+ */
34
+ const geoOf = (
35
+ prop: ConsentGeoOptions | undefined, context: AppContext<AppConfig> & Partial<ConsentGeoPluginAppend>
36
+ ): ConsentGeoOptions | undefined =>
37
+ prop ?? context.cfg.cookieConsent?.geo ?? (typeof context.consentGeo === 'function' ? {} : undefined)
38
+
39
+ void _localeParity
40
+
28
41
  /**
29
42
  * The consent dialog, bound to this application's language and translations.
30
43
  *
@@ -44,19 +57,28 @@ const MenuAwareCookieConsent: FC<CookieConsentProps> = props =>
44
57
  * while the context is built, before anything renders.
45
58
  */
46
59
  export const PanelCookieConsent: FC<CookieConsentProps> = props => {
47
- const context = useContext<AppConfig, AppContext<AppConfig> & Partial<ConsentWidgetServiceAppend>>()
60
+ const context = useContext<
61
+ AppConfig, AppContext<AppConfig> & Partial<ConsentWidgetServiceAppend & ConsentGeoPluginAppend>
62
+ >()
63
+ const mode = props.mode ?? context.cfg.cookieConsent?.mode
64
+ const geo = geoOf(props.geo, context)
65
+ const bound = { ...props, ...(mode != null ? { mode } : {}), ...(geo != null ? { geo } : {}) }
48
66
 
49
67
  return typeof context.consentWidget === 'function'
50
- ? <MenuAwareCookieConsent {...props} />
51
- : <BoundCookieConsent {...props} />
68
+ ? <MenuAwareCookieConsent {...bound} />
69
+ : <BoundCookieConsent {...bound} />
52
70
  }
53
71
 
72
+ /**
73
+ * The cookie-policy page, bound like the dialog — and told about the geo gate the same way, so it
74
+ * states the regional rule exactly when the dialog applies it.
75
+ */
54
76
  export const PanelCookiePolicy: FC<CookiePolicyProps> = props => {
77
+ const context = useContext<AppConfig, AppContext<AppConfig> & Partial<ConsentGeoPluginAppend>>()
55
78
  const [lng] = useLanguage()
56
79
  const locale = props.locale ?? lng
57
80
  const translate = useConsentTranslate(locale, props.translate)
81
+ const geo = geoOf(props.geo, context)
58
82
 
59
- return <CookiePolicy {...props} locale={locale} translate={translate} />
83
+ return <CookiePolicy {...props} {...(geo != null ? { geo } : {})} locale={locale} translate={translate} />
60
84
  }
61
-
62
- void _localeParity
@@ -0,0 +1,26 @@
1
+ import { consentPluginHelper, type ConsentGeoPlugin } from '@owlmeans/web-consent'
2
+ import type { ClientConfig } from '@owlmeans/client-context'
3
+ import type { ClientContext } from '@owlmeans/client'
4
+ import type { ConsentGeoPluginAppend } from './types.js'
5
+
6
+ /**
7
+ * Add the application's own country locator while its context is configured — beside
8
+ * `appendConsentWidgetService`, before anything renders.
9
+ *
10
+ * A locator is one async method that resolves the visitor's country or throws
11
+ * (`ConsentGeoPlugin`). It is registered into `@owlmeans/consent`'s plugin registry, ahead of the
12
+ * built-in Cloudflare one, and `PanelCookieConsent` turns the geo gate on for it even where
13
+ * `cfg.cookieConsent.geo` says nothing — appending a locator IS asking for the gate. Its result
14
+ * decides as Cloudflare's would: a visitor in a consent country is asked, anyone else is decided for
15
+ * automatically, and a throw hands over to the next locator — with none left, the visitor is asked.
16
+ */
17
+ export const appendConsentGeoPlugin = <C extends ClientConfig, T extends ClientContext<C>>(
18
+ ctx: T, plugin: ConsentGeoPlugin
19
+ ): T & ConsentGeoPluginAppend => {
20
+ const _ctx = ctx as T & ConsentGeoPluginAppend
21
+
22
+ consentPluginHelper.registerConsentPlugin(plugin)
23
+ _ctx.consentGeo = () => plugin
24
+
25
+ return _ctx
26
+ }
@@ -3,9 +3,11 @@ export * from './menu-widget.js'
3
3
  export * from './presence.js'
4
4
  export { installConsentLanguage } from './language.js'
5
5
  export { createConsentWidgetService, appendConsentWidgetService, CONSENT_WIDGET_STATE } from './service.js'
6
+ export { appendConsentGeoPlugin } from './geo.js'
6
7
  export { CONSENT_WIDGET_SERVICE } from './consts.js'
7
8
  export type {
8
- ConsentWidgetPresenceRecord, ConsentWidgetService, ConsentWidgetServiceAppend
9
+ ConsentWidgetPresenceRecord, ConsentWidgetService, ConsentWidgetServiceAppend,
10
+ PanelCookieConsentConfig, ConsentGeoPluginAppend,
9
11
  } from './types.js'
10
12
  export {
11
13
  useConsent, useConsentCategory, consentStore, openConsent, isConsented,
@@ -13,8 +15,10 @@ export {
13
15
  CONSENT_KEY, CONSENT_ESSENTIAL, CONSENT_ANALYTICS, CONSENT_MARKETING,
14
16
  CONSENT_EVENT,
15
17
  consentBootstrapScript,
18
+ CONSENT_REQUIRED_COUNTRIES,
16
19
  } from '@owlmeans/web-consent'
17
20
  export type {
18
21
  ConsentCategory, ConsentMenuWidgetProps, ConsentOptions, ConsentRecord, ConsentSignal,
19
- ConsentState, CookieConsentProps, CookiePolicyProps,
22
+ ConsentState, CookieConsentProps, CookiePolicyProps, ConsentDisplayMode, ConsentGeoOptions,
23
+ ConsentGeoPlugin, ConsentGeoLocation,
20
24
  } from '@owlmeans/web-consent'
@@ -1,6 +1,6 @@
1
1
  import { useCallback } from 'react'
2
2
  import { useI18nLib } from '@owlmeans/client-i18n'
3
- import { consentI18nHelper } from '@owlmeans/consent'
3
+ import { consentI18nHelper } from '@owlmeans/web-consent'
4
4
 
5
5
  /**
6
6
  * Resolve consent copy through the application first, and the packaged bundle second.
@@ -1,5 +1,27 @@
1
1
  import type { ResourceRecord } from '@owlmeans/resource'
2
2
  import type { InitializedService } from '@owlmeans/context'
3
+ import type { ConsentDisplayMode, ConsentGeoOptions, ConsentGeoPlugin } from '@owlmeans/web-consent'
4
+
5
+ /**
6
+ * `cfg.cookieConsent` — how the application's cookie consent asks, set where the rest of its
7
+ * configuration is. A prop on `PanelCookieConsent` / `PanelCookiePolicy` always wins.
8
+ */
9
+ export interface PanelCookieConsentConfig {
10
+ /** `bar` (the default) or `window` — see `ConsentDisplayMode`. */
11
+ mode?: ConsentDisplayMode
12
+ /**
13
+ * Ask only where the law requires it. `{ cloudflare: true }` locates the visitor through the
14
+ * Cloudflare edge the application is served from; a locator of the application's own is added in
15
+ * code with `appendConsentGeoPlugin`. See `ConsentGeoOptions`.
16
+ */
17
+ geo?: ConsentGeoOptions
18
+ }
19
+
20
+ /** What `appendConsentGeoPlugin` adds to the context. */
21
+ export interface ConsentGeoPluginAppend {
22
+ /** The application's own locator, registered while the context was configured. */
23
+ consentGeo: () => ConsentGeoPlugin
24
+ }
3
25
 
4
26
  /** A `single`-store presence flag: one record, no id needed. */
5
27
  export interface ConsentWidgetPresenceRecord extends ResourceRecord {
package/src/types.ts CHANGED
@@ -3,6 +3,7 @@ import type { WithFlowConfig } from '@owlmeans/flow'
3
3
  import type { AppConfig as Config, AppContext as Context, RenderOptions } from '@owlmeans/web-client'
4
4
  import type { FlowService } from '@owlmeans/web-flow'
5
5
  import type { SocketClientSettings, SocketStatusServiceAppend } from '@owlmeans/client-socket'
6
+ import type { PanelCookieConsentConfig } from './consent/types.js'
6
7
 
7
8
  export interface AppConfig extends Config, WithFlowConfig {
8
9
  socket?: SocketClientSettings & {
@@ -14,6 +15,8 @@ export interface AppConfig extends Config, WithFlowConfig {
14
15
  */
15
16
  reloadDialog?: boolean
16
17
  }
18
+ /** How the cookie consent asks — the bar or the window, and where (`geo`). See `./consent`. */
19
+ cookieConsent?: PanelCookieConsentConfig
17
20
  }
18
21
 
19
22
  export interface AppContext<C extends AppConfig = AppConfig> extends Context<C>, SocketStatusServiceAppend {
@@ -59,4 +59,54 @@ describe('@owlmeans/web-panel/consent — the dialog and the menu widget', () =>
59
59
  await close()
60
60
  }
61
61
  }, TIMEOUT)
62
+
63
+ test('a first-time visitor meets the bar by default', async () => {
64
+ const { page, close } = await open('/prefs?consent=bare')
65
+ try {
66
+ await page.locator('[data-consent-bar]').waitFor()
67
+ expect(await page.locator('[data-consent-dialog]').getAttribute('data-consent-mode')).toBe('bar')
68
+ } finally {
69
+ await close()
70
+ }
71
+ }, TIMEOUT)
72
+
73
+ test('cfg.cookieConsent.mode picks the window, and a mode prop wins over it', async () => {
74
+ const configured = await open('/prefs?consent=bare&consentCfg=window')
75
+ try {
76
+ await configured.page.locator('[data-consent-dialog][data-consent-mode="window"]').waitFor()
77
+ } finally {
78
+ await configured.close()
79
+ }
80
+
81
+ const overridden = await open('/prefs?consent=bare&consentCfg=window&modeProp=bar')
82
+ try {
83
+ await overridden.page.locator('[data-consent-bar]').waitFor()
84
+ } finally {
85
+ await overridden.close()
86
+ }
87
+ }, TIMEOUT)
88
+
89
+ test('a locator appended to the context turns the geo gate on by itself', async () => {
90
+ const outside = await open('/prefs?consent=bare&consentGeo=US')
91
+ try {
92
+ await outside.page.waitForFunction(() => document.documentElement.getAttribute('data-consent') === 'decided')
93
+ expect(await outside.page.locator('[data-consent-dialog]').count()).toBe(0)
94
+ } finally {
95
+ await outside.close()
96
+ }
97
+
98
+ const inside = await open('/prefs?consent=bare&consentGeo=DE')
99
+ try {
100
+ await inside.page.locator('[data-consent-bar]').waitFor()
101
+ } finally {
102
+ await inside.close()
103
+ }
104
+
105
+ const unknown = await open('/prefs?consent=bare&consentGeo=fail')
106
+ try {
107
+ await unknown.page.locator('[data-consent-bar]').waitFor()
108
+ } finally {
109
+ await unknown.close()
110
+ }
111
+ }, TIMEOUT)
62
112
  })
@@ -18,7 +18,8 @@ import {
18
18
  } from '../../src/index.js'
19
19
  import { LoginScreen } from '../../src/components/login/index.js'
20
20
  import {
21
- PanelCookieConsent, PanelConsentMenuWidget, appendConsentWidgetService, useConsentMenuPresence
21
+ PanelCookieConsent, PanelConsentMenuWidget, appendConsentWidgetService, useConsentMenuPresence,
22
+ appendConsentGeoPlugin,
22
23
  } from '../../src/consent/index.js'
23
24
  import { LoginOutcome, ensureLoginService } from '@owlmeans/client-auth/login'
24
25
  import type { LoginMethod } from '@owlmeans/client-auth/login'
@@ -87,10 +88,18 @@ const method = (id: string, over?: Partial<LoginMethod>): LoginMethod => ({
87
88
  })
88
89
 
89
90
  /** The sign-in screen, with the terms confirmation the configuration requires. */
90
- const LoginHarness: FC = () => <LoginScreen
91
- Logo={() => <span id="login-logo">logo</span>}
92
- translate={(_key, defaultValue) => defaultValue}
93
- />
91
+ const LoginHarness: FC = () => {
92
+ const [locale, setLocale] = useState<string | undefined>(localizedTerms ? 'pl' : undefined)
93
+
94
+ return <>
95
+ {localizedTerms && <button id="login-language-fr" onClick={() => setLocale('fr')}>Français</button>}
96
+ <LoginScreen
97
+ Logo={() => <span id="login-logo">logo</span>}
98
+ translate={(_key, defaultValue) => defaultValue}
99
+ locale={locale}
100
+ />
101
+ </>
102
+ }
94
103
 
95
104
  // `?header=broken` simulates the layout-restyle bug this harness pins: a `headerClassName`
96
105
  // carrying an invalid Tailwind v4 arbitrary-value background (v3 syntax, silently dropped) and a
@@ -110,6 +119,12 @@ const mobileMenu = new URLSearchParams(window.location.search).get('mobileMenu')
110
119
  // service — an application using the dialog on its own. `?consent=menu` appends the service, so
111
120
  // the footer's "Cookie settings" control takes over the floating button's job.
112
121
  const consentMode = new URLSearchParams(window.location.search).get('consent')
122
+ // `?consentCfg=window|bar` sets `cfg.cookieConsent.mode`; `?modeProp=bar|window` passes `mode` as a
123
+ // prop on top of it (the prop must win). `?consentGeo=<CC>|fail` appends a context-level locator
124
+ // with `appendConsentGeoPlugin` — the gate turns on with no `geo` configured anywhere.
125
+ const consentCfg = new URLSearchParams(window.location.search).get('consentCfg')
126
+ const modeProp = new URLSearchParams(window.location.search).get('modeProp')
127
+ const consentGeo = new URLSearchParams(window.location.search).get('consentGeo')
113
128
  // `?skip=off` passes `skipLinkLabel={false}` — an application that renders its own skip link.
114
129
  const skipOff = new URLSearchParams(window.location.search).get('skip') === 'off'
115
130
  // `?themeToggle=1` asks for the footer's light/dark switcher. Absent, the prop is not passed.
@@ -118,6 +133,8 @@ const themeToggle = new URLSearchParams(window.location.search).get('themeToggle
118
133
  // confirmation — the shape that pins the "still exactly one checkbox" and "documents render
119
134
  // outside the notice" rules even when there is more than terms+privacy to show.
120
135
  const extendedTerms = new URLSearchParams(window.location.search).get('terms') === 'extended'
136
+ // `?terms=localized` selects known translated destinations using the live screen locale.
137
+ const localizedTerms = new URLSearchParams(window.location.search).get('terms') === 'localized'
121
138
  // `?defer=bound` registers a step that CONFIRMS the terms, bound to a real screen — the sign-in
122
139
  // screen must then show no checkbox, only the privacy notice, and block nothing.
123
140
  // `?defer=unbound` registers the same step but never binds its entrypoint — the checkbox must
@@ -281,6 +298,16 @@ base.security = {
281
298
  // is given, and blocking must SAY so rather than swallow the click.
282
299
  terms: {
283
300
  required: true, terms: 'https://example.test/terms', privacy: 'https://example.test/privacy',
301
+ ...(localizedTerms ? {
302
+ billing: { href: 'https://example.test/billing', revisedAt: '2026-10-04' },
303
+ cookies: 'https://example.test/cookies', showRevision: true,
304
+ localizedHrefs: {
305
+ terms: { pl: 'https://example.test/pl/terms', fr: 'https://example.test/fr/terms' },
306
+ billing: { pl: 'https://example.test/pl/billing', fr: 'https://example.test/fr/billing' },
307
+ privacy: { pl: 'https://example.test/pl/privacy', fr: 'https://example.test/fr/privacy' },
308
+ cookies: { pl: 'https://example.test/pl/cookies', fr: 'https://example.test/fr/cookies' },
309
+ },
310
+ } : {}),
284
311
  ...(extendedTerms ? {
285
312
  billing: { href: 'https://example.test/billing', revisedAt: '2026-01-01' },
286
313
  product: { name: 'Harness', href: 'https://example.test/product' },
@@ -309,6 +336,9 @@ if (providerPlacement != null) {
309
336
  // test that needs the OFF case loads `?reloadDialog=0` — one harness process, both branches.
310
337
  const reloadDialogEnabled = new URLSearchParams(window.location.search).get('reloadDialog') !== '0'
311
338
  ;(base as { socket?: { reloadDialog?: boolean } }).socket = { reloadDialog: reloadDialogEnabled }
339
+ if (consentCfg === 'window' || consentCfg === 'bar') {
340
+ ;(base as { cookieConsent?: { mode: 'window' | 'bar' } }).cookieConsent = { mode: consentCfg }
341
+ }
312
342
 
313
343
  // `ready` stays false: the Router compiles the entrypoint tree into routes ONLY while the
314
344
  // context is un-initialized, so a pre-readied context renders a blank page.
@@ -319,6 +349,18 @@ context.serviceRoute(API, true)
319
349
  if (consentMode === 'menu') {
320
350
  appendConsentWidgetService(context as never)
321
351
  }
352
+ if (consentGeo != null) {
353
+ appendConsentGeoPlugin(context as never, {
354
+ alias: 'harness-geo',
355
+ locate: async () => {
356
+ if (consentGeo === 'fail') {
357
+ throw new Error('the harness locator cannot tell')
358
+ }
359
+
360
+ return { country: consentGeo }
361
+ },
362
+ })
363
+ }
322
364
 
323
365
  ensureLoginService(context as never).registerMethodSource({
324
366
  alias: 'harness',
@@ -390,5 +432,6 @@ createRoot(document.getElementById('root')!).render(<PanelApp context={context a
390
432
  {consentMode != null && <PanelCookieConsent
391
433
  policyHref="/cookies"
392
434
  {...(withDomains ? { linker: { domains: ['harness-partner.test'] } } : {})}
435
+ {...(modeProp === 'window' || modeProp === 'bar' ? { mode: modeProp } : {})}
393
436
  />}
394
437
  </PanelApp>)
@@ -191,6 +191,28 @@ describe('@owlmeans/web-panel — the sign-in screen', () => {
191
191
  }
192
192
  }, TIMEOUT)
193
193
 
194
+ test('login documents and the separate privacy notice follow an explicit live PL/FR locale map', async () => {
195
+ const { page, close } = await open('?terms=localized')
196
+ try {
197
+ for (const key of ['terms', 'billing', 'privacy', 'cookies']) {
198
+ expect(await page.locator(`[data-login-document="${key}"]`).getAttribute('href'))
199
+ .toBe(`https://example.test/pl/${key}`)
200
+ }
201
+ expect(await page.locator('[data-login-privacy] a').count()).toBe(2)
202
+ expect(await page.locator('label:has([data-login-terms]) [data-login-privacy]').count()).toBe(0)
203
+ const revision = await page.locator('[data-login-revised]').textContent()
204
+ await page.locator('#login-language-fr').click()
205
+ for (const key of ['terms', 'billing', 'privacy', 'cookies']) {
206
+ expect(await page.locator(`[data-login-document="${key}"]`).getAttribute('href'))
207
+ .toBe(`https://example.test/fr/${key}`)
208
+ }
209
+ expect(await page.locator('[data-login-revised]').textContent()).toBe(revision)
210
+ expect(await page.locator('[data-login-terms]').count()).toBe(1)
211
+ } finally {
212
+ await close()
213
+ }
214
+ }, TIMEOUT)
215
+
194
216
  test('[data-login-revised] is absent when the configuration never asked to show it', async () => {
195
217
  const { page, close } = await open()
196
218
  try {