@peax/consent-banner-shared 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,4 +8,14 @@ The root exports `BannerOptions`, `BannerMsg`, preference section and navigation
8
8
 
9
9
  `transitionPreferencesView` receives explicit opening, dismissal, and controlled-input synchronization actions. Synchronize when visibility or floating-button availability changes; synchronizing on every render would discard a floating request awaiting application acknowledgement.
10
10
 
11
- ESM and TypeScript declarations are published at the root. Locales is the only dependency. There are no stylesheet exports or import-time side effects.
11
+ ESM and TypeScript declarations are published at the root. There are no required dependencies and no dependency on the optional locales package. There are no stylesheet exports or import-time side effects.
12
+
13
+ ## Localization
14
+
15
+ The root also exports `parseMessage`, `MessagePart`, `BannerMessages`, `BannerLocale`, `BuiltInPreferencesSectionId`, `PreferencesMessages`, `LocaleMessages`, `LocaleContent`, `PreferencesLocaleContent`, `BannerLocales`, `ResolvedLocaleContent`, `defaultEnglishLocale`, and `resolveLocaleContent`.
16
+
17
+ `resolveLocaleContent(locale = 'en', locales?, hasContentOverrides = false)` returns `{ locale, banner, preferences }`. It matches normalized exact/base tags, fills missing copy from independent English defaults, and supports custom languages and preference IDs. No dictionary package is imported. Applications normally pass `locales`, `bannerContent`, and `preferencesContent` to their adapter instead of calling the helper directly.
18
+
19
+ `parseMessage(message, allowlist)` returns ordered text/link parts with source offsets. The allowlist is required. Unknown, nested, attributed, or malformed placeholders remain literal text; messages are never interpreted as HTML. The parser moved here from locales without changing its behavior.
20
+
21
+ See the [localization guide](https://github.com/peaxco/consent/blob/main/docs/localization.md).
package/dist/index.d.ts CHANGED
@@ -1,6 +1,19 @@
1
- import { BannerMessages } from '@peax/consent-banner-locales';
2
- import { BuiltInPreferencesSectionId } from '@peax/consent-banner-locales';
3
- import { PreferencesMessages } from '@peax/consent-banner-locales';
1
+ export declare type BannerLocale = 'bg' | 'cs' | 'da' | 'de' | 'el' | 'en' | 'es' | 'et' | 'fi' | 'fr' | 'ga' | 'hr' | 'hu' | 'it' | 'lt' | 'lv' | 'mt' | 'nl' | 'pl' | 'pt' | 'ro' | 'ru' | 'sk' | 'sl' | 'sv';
2
+
3
+ export declare type BannerLocales = Readonly<Record<string, LocaleContent | undefined>>;
4
+
5
+ export declare type BannerMessages = Readonly<{
6
+ title: string;
7
+ collapsedText: string;
8
+ acceptAllLabel: string;
9
+ rejectAllLabel: string;
10
+ customizePreferencesLabel: string;
11
+ preferencesTitle: string;
12
+ preferencesText?: string;
13
+ closePreferencesLabel: string;
14
+ applyPreferencesLabel: string;
15
+ cookiePolicyLabel: string;
16
+ }>;
4
17
 
5
18
  export declare type BannerMsg = PreferencesNavigationMsg | {
6
19
  type: 'on_accept_all' | 'on_reject_all';
@@ -21,6 +34,7 @@ export declare type BannerOptions = Placement & FloatingButtonOptions & {
21
34
  showCustomizePreferences?: boolean;
22
35
  variant?: 'compact' | 'stacked';
23
36
  locale?: string;
37
+ locales?: BannerLocales;
24
38
  cookiePolicyUrl?: string;
25
39
  bannerContent?: Partial<BannerMessages>;
26
40
  preferencesSections?: readonly PreferencesSectionId[];
@@ -28,6 +42,10 @@ export declare type BannerOptions = Placement & FloatingButtonOptions & {
28
42
  theme?: 'light' | 'dark' | 'auto';
29
43
  };
30
44
 
45
+ export declare type BuiltInPreferencesSectionId = 'essential' | 'analytics' | 'marketing' | 'functional' | 'personalization' | 'social' | 'security';
46
+
47
+ export declare const defaultEnglishLocale: LocaleMessages;
48
+
31
49
  export declare const defaultPreferencesSections: readonly ["essential", "analytics", "marketing"];
32
50
 
33
51
  export declare const entranceDuration = 0.36;
@@ -44,10 +62,33 @@ export declare type FloatingButtonOptions = {
44
62
 
45
63
  export declare type FloatingButtonPosition = 'left' | 'right';
46
64
 
65
+ export declare type LocaleContent = Readonly<{
66
+ banner?: Partial<BannerMessages>;
67
+ preferences?: PreferencesLocaleContent;
68
+ }>;
69
+
70
+ /** Complete copy shipped by a built-in language entry. */
71
+ export declare type LocaleMessages = Readonly<{
72
+ banner: BannerMessages;
73
+ preferences: PreferencesMessages;
74
+ }>;
75
+
47
76
  export declare function mergeBannerContent(messages: BannerMessages, overrides?: Partial<BannerMessages>): BannerMessages;
48
77
 
78
+ export declare type MessagePart<Link extends string = string> = Readonly<{
79
+ offset: number;
80
+ value: string;
81
+ } & ({
82
+ type: 'text';
83
+ } | {
84
+ type: 'link';
85
+ name: Link;
86
+ })>;
87
+
49
88
  export declare function normalizeAppearanceDelay(delay: number): number;
50
89
 
90
+ export declare function parseMessage<Link extends string>(message: string, links: readonly Link[]): MessagePart<Link>[];
91
+
51
92
  export declare type Placement = {
52
93
  position?: 'right';
53
94
  slideFrom?: 'bottom' | 'right';
@@ -59,6 +100,17 @@ export declare type Placement = {
59
100
  slideFrom?: 'bottom';
60
101
  };
61
102
 
103
+ /** Application copy can translate custom sections and omit any field. */
104
+ export declare type PreferencesLocaleContent = Readonly<Record<string, Readonly<{
105
+ title?: string;
106
+ text?: string;
107
+ }> | undefined>>;
108
+
109
+ export declare type PreferencesMessages = Readonly<Record<BuiltInPreferencesSectionId, Readonly<{
110
+ title: string;
111
+ text: string;
112
+ }>>>;
113
+
62
114
  export declare type PreferencesNavigationMsg = {
63
115
  type: 'on_open_preferences' | 'on_close_preferences';
64
116
  source: PreferencesSource;
@@ -96,13 +148,22 @@ export declare type PreferencesViewAction = {
96
148
  /** Keep edits to surviving enabled sections; disabled values always follow the application. */
97
149
  export declare function reconcileDraft(sections: readonly ResolvedPreferencesSection[], draft?: ReadonlyMap<string, boolean>): Map<string, boolean>;
98
150
 
151
+ export declare type ResolvedLocaleContent = Readonly<{
152
+ locale: string;
153
+ banner: BannerMessages;
154
+ preferences: PreferencesLocaleContent;
155
+ }>;
156
+
99
157
  export declare type ResolvedPreferencesSection = Required<PreferencesSection> & {
100
158
  readonly id: PreferencesSectionId;
101
159
  };
102
160
 
103
161
  export declare function resolveFloatingPosition(position: string | undefined, override?: FloatingButtonPosition): FloatingButtonPosition;
104
162
 
105
- export declare function resolveSections(ids: readonly PreferencesSectionId[], content: Readonly<Partial<Record<PreferencesSectionId, PreferencesSection>>> | undefined, messages: PreferencesMessages): ResolvedPreferencesSection[];
163
+ /** Resolve application-owned dictionaries without importing the optional locales package. */
164
+ export declare function resolveLocaleContent(locale?: string, locales?: BannerLocales, hasContentOverrides?: boolean): ResolvedLocaleContent;
165
+
166
+ export declare function resolveSections(ids: readonly PreferencesSectionId[], content: Readonly<Partial<Record<PreferencesSectionId, PreferencesSection>>> | undefined, messages: PreferencesLocaleContent): ResolvedPreferencesSection[];
106
167
 
107
168
  export declare function resolveSlideFrom(position: string, slideFrom: string): 'bottom' | 'left' | 'right';
108
169
 
package/dist/index.js CHANGED
@@ -1,10 +1,137 @@
1
+ //#region src/defaultEnglishLocale.ts
2
+ var e = {
3
+ banner: {
4
+ title: "We use cookies",
5
+ collapsedText: "We use cookies to learn how you interact with our content to make your experience better with <cookiePolicy>Cookie Policy</cookiePolicy> in place.",
6
+ acceptAllLabel: "Accept all",
7
+ rejectAllLabel: "Reject all",
8
+ customizePreferencesLabel: "Customize preferences",
9
+ preferencesTitle: "Cookie Preferences",
10
+ closePreferencesLabel: "Close preferences",
11
+ applyPreferencesLabel: "Apply preferences",
12
+ cookiePolicyLabel: "Cookie policy"
13
+ },
14
+ preferences: {
15
+ essential: {
16
+ title: "Essential",
17
+ text: "Essential cookies are used to ensure the proper functioning of the website. They are necessary and cannot be disabled."
18
+ },
19
+ analytics: {
20
+ title: "Analytics",
21
+ text: "Analytical cookies are used to analyze performance and user behaviour to provide the most relevant experience. We use analytics tools to collect and analyze this data."
22
+ },
23
+ marketing: {
24
+ title: "Marketing",
25
+ text: "Marketing cookies are used to show relevant advertisements and measure the effectiveness of advertising campaigns."
26
+ },
27
+ functional: {
28
+ title: "Functional",
29
+ text: "Functional cookies enable optional website features, such as embedded videos and live chat."
30
+ },
31
+ personalization: {
32
+ title: "Personalization",
33
+ text: "Personalization cookies remember your preferences and adapt content and recommendations to your interests."
34
+ },
35
+ social: {
36
+ title: "Social media",
37
+ text: "Social media cookies enable sharing features and embedded content from social platforms. These platforms may use them to track your activity across websites."
38
+ },
39
+ security: {
40
+ title: "Security",
41
+ text: "Security cookies support optional fraud detection and additional protection features. Cookies required for basic website security are included under Essential."
42
+ }
43
+ }
44
+ }, t = .36, n = [
45
+ .16,
46
+ 1,
47
+ .3,
48
+ 1
49
+ ];
50
+ function r(e, t) {
51
+ return t ?? (e === "left" ? "left" : "right");
52
+ }
53
+ function i(e, t) {
54
+ return t === e && (t === "left" || t === "right") ? t : "bottom";
55
+ }
56
+ function a(e) {
57
+ return Number.isFinite(e) ? Math.max(0, e) : 0;
58
+ }
59
+ function o(e, t) {
60
+ return {
61
+ ...e,
62
+ ...Object.fromEntries(Object.entries(t ?? {}).filter(([, e]) => e != null))
63
+ };
64
+ }
65
+ function s(e) {
66
+ if (e?.trim()) try {
67
+ let t = new URL(e, "https://consent.invalid");
68
+ return t.protocol === "http:" || t.protocol === "https:" ? e : void 0;
69
+ } catch {
70
+ return;
71
+ }
72
+ }
73
+ //#endregion
74
+ //#region src/localization.ts
75
+ function c(t = "en", n, r = !1) {
76
+ let i = t.trim().toLowerCase() || "en", a = new Map(Object.entries(n ?? {}).map(([e, t]) => [e.trim().toLowerCase(), t])), s = i.split("-")[0] ?? i, c = a.get(i) ? i : a.get(s) ? s : void 0, l = { ...c ? a.get(c) : void 0 }, u = new Map(Object.entries(e.preferences)), d = new Map(Object.entries(l?.preferences ?? {})), f = /* @__PURE__ */ new Set([...u.keys(), ...d.keys()]), p = Object.fromEntries([...f].map((e) => {
77
+ let t = u.get(e), n = { ...d.get(e) };
78
+ return [e, {
79
+ title: n?.title?.trim() ? n.title : t?.title ?? e,
80
+ text: n?.text ?? t?.text ?? ""
81
+ }];
82
+ }));
83
+ return {
84
+ locale: c ?? (r ? i : "en"),
85
+ banner: o(e.banner, l?.banner),
86
+ preferences: p
87
+ };
88
+ }
89
+ //#endregion
90
+ //#region src/parseMessage.ts
91
+ function l(e, t) {
92
+ let n = [], r = t.map((e) => ({
93
+ name: e,
94
+ opening: `<${e}>`,
95
+ closing: `</${e}>`
96
+ })), i = 0, a = 0;
97
+ for (; a < e.length;) {
98
+ let t;
99
+ for (let n of r) {
100
+ let r = e.indexOf(n.opening, a);
101
+ r !== -1 && (!t || r < t.start) && (t = {
102
+ ...n,
103
+ start: r
104
+ });
105
+ }
106
+ if (!t) break;
107
+ let { name: o, opening: s, closing: c, start: l } = t, u = e.indexOf(c, l + s.length);
108
+ if (u === -1) break;
109
+ let d = e.slice(l + s.length, u);
110
+ a = u + c.length, !/[<>]/.test(d) && (l > i && n.push({
111
+ type: "text",
112
+ value: e.slice(i, l),
113
+ offset: i
114
+ }), n.push({
115
+ type: "link",
116
+ name: o,
117
+ value: d,
118
+ offset: l
119
+ }), i = a);
120
+ }
121
+ return i < e.length && n.push({
122
+ type: "text",
123
+ value: e.slice(i),
124
+ offset: i
125
+ }), n;
126
+ }
127
+ //#endregion
1
128
  //#region src/preferences.ts
2
- var e = [
129
+ var u = [
3
130
  "essential",
4
131
  "analytics",
5
132
  "marketing"
6
133
  ];
7
- function t(e, t, n) {
134
+ function d(e, t, n) {
8
135
  let r = new Map(Object.entries(n));
9
136
  return [...new Set(e.filter((e) => e.trim()))].map((e) => {
10
137
  let n = r.get(e), i = t && Object.hasOwn(t, e) ? t[e] : void 0;
@@ -17,10 +144,10 @@ function t(e, t, n) {
17
144
  };
18
145
  });
19
146
  }
20
- function n(e, t = /* @__PURE__ */ new Map()) {
147
+ function f(e, t = /* @__PURE__ */ new Map()) {
21
148
  return new Map(e.map((e) => [e.id, e.disabled ? e.checked : t.get(e.id) ?? e.checked]));
22
149
  }
23
- function r(e, t) {
150
+ function p(e, t) {
24
151
  switch (t.type) {
25
152
  case "open": return t.source === "banner" ? { source: "banner" } : {
26
153
  source: "floating-button",
@@ -31,35 +158,4 @@ function r(e, t) {
31
158
  }
32
159
  }
33
160
  //#endregion
34
- //#region src/presentation.ts
35
- var i = .36, a = [
36
- .16,
37
- 1,
38
- .3,
39
- 1
40
- ];
41
- function o(e, t) {
42
- return t ?? (e === "left" ? "left" : "right");
43
- }
44
- function s(e, t) {
45
- return t === e && (t === "left" || t === "right") ? t : "bottom";
46
- }
47
- function c(e) {
48
- return Number.isFinite(e) ? Math.max(0, e) : 0;
49
- }
50
- function l(e, t) {
51
- return {
52
- ...e,
53
- ...Object.fromEntries(Object.entries(t ?? {}).filter(([, e]) => e != null))
54
- };
55
- }
56
- function u(e) {
57
- if (e?.trim()) try {
58
- let t = new URL(e, "https://consent.invalid");
59
- return t.protocol === "http:" || t.protocol === "https:" ? e : void 0;
60
- } catch {
61
- return;
62
- }
63
- }
64
- //#endregion
65
- export { e as defaultPreferencesSections, i as entranceDuration, a as entranceEase, l as mergeBannerContent, c as normalizeAppearanceDelay, n as reconcileDraft, o as resolveFloatingPosition, t as resolveSections, s as resolveSlideFrom, u as safeHref, r as transitionPreferencesView };
161
+ export { e as defaultEnglishLocale, u as defaultPreferencesSections, t as entranceDuration, n as entranceEase, o as mergeBannerContent, a as normalizeAppearanceDelay, l as parseMessage, f as reconcileDraft, r as resolveFloatingPosition, c as resolveLocaleContent, d as resolveSections, i as resolveSlideFrom, s as safeHref, p as transitionPreferencesView };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peax/consent-banner-shared",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Framework-independent presentation contracts and UI state helpers for Peax Consent banners.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/peaxco/consent",
@@ -38,14 +38,11 @@
38
38
  }
39
39
  },
40
40
  "devDependencies": {
41
- "@microsoft/api-extractor": "^7.59.0",
42
- "@types/node": "^26.3.0",
41
+ "@microsoft/api-extractor": "^7.59.3",
42
+ "@types/node": "^26.6.4",
43
43
  "typescript": "5.9.3",
44
- "vite": "^8.2.2",
45
- "vite-plugin-dts": "^5.0.3",
46
- "vitest": "^4.1.11"
47
- },
48
- "dependencies": {
49
- "@peax/consent-banner-locales": "^0.4.0"
44
+ "vite": "^8.3.2",
45
+ "vite-plugin-dts": "^5.1.1",
46
+ "vitest": "^5.0.3"
50
47
  }
51
48
  }