@tantainnovative/ndpr-toolkit 3.5.5 → 3.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/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
4
4
 
5
+ ## [3.6.0](https://github.com/mr-tanta/ndpr-toolkit/compare/v3.5.5...v3.6.0) (2026-05-24)
6
+
7
+ ### Features (developer feedback)
8
+
9
+ This release lands changes flagged by integrating teams using the toolkit in production. All additions are backward-compatible — 3.5.x consumers can upgrade without changes.
10
+
11
+ * **`apiAdapter` is now production-ready.** Adds `credentials` (defaults to `'same-origin'`, set `'include'` for cross-origin), dynamic `headers` (function form for runtime CSRF token lookup), `loadMethod`/`saveMethod` overrides (e.g. `PUT` for upsert APIs), `unwrap` (transform `{ data: ... }` envelopes), configurable `retry` with exponential backoff and a `shouldRetry` predicate (default: retry on network errors + 5xx, skip 4xx), and `onError`/`onSuccess` hooks for telemetry. The pre-3.6.0 `console.warn` behavior is preserved when no `onError` is configured.
12
+ * **`NDPRConsent` exposes a `copy` prop.** Override `title` / `description` / `acceptAll` / `rejectAll` / `customize` / `save` strings without dropping to the lower-level `<ConsentBanner>` API.
13
+ * **`NDPRSubjectRights` adds public-form `submitTo` mode.** Public sites can POST to their backend instead of being state-managed by an adapter. Pairs with `submitOptions` (credentials, headers) and `onSubmitError`. The state-managed `adapter` mode is unchanged.
14
+ * **Per-preset subpath entries** for bundle-size-sensitive consumers:
15
+ - `@tantainnovative/ndpr-toolkit/presets/consent` — just `NDPRConsent` (~4KB vs ~8KB for the full barrel)
16
+ - `@tantainnovative/ndpr-toolkit/presets/dsr` — just `NDPRSubjectRights`
17
+ - `@tantainnovative/ndpr-toolkit/presets/policy` — just `NDPRPrivacyPolicy`
18
+ The full `/presets` barrel is unchanged. These are additive narrower entries.
19
+
20
+ ### Docs
21
+
22
+ * README install block now shows Bun, npm, and Yarn alongside pnpm.
23
+
24
+ ### Coming next (3.6.1+)
25
+
26
+ - Recipe pages for ecommerce / newsletter / contact-form / careers / admin DSR patterns
27
+ - Org-specific privacy policy templates (SaaS, ecommerce, school, healthcare, procurement)
28
+ - Continued bundle reduction
29
+
5
30
  ## [3.5.5](https://github.com/mr-tanta/ndpr-toolkit/compare/v3.5.4...v3.5.5) (2026-05-24)
6
31
 
7
32
  ### Features (tests + types)
package/README.md CHANGED
@@ -74,14 +74,26 @@ That's it. NDPA-compliant consent with server-side persistence in under 20 lines
74
74
 
75
75
  ## Install
76
76
 
77
+ Pick your package manager:
78
+
77
79
  ```bash
80
+ # pnpm
78
81
  pnpm add @tantainnovative/ndpr-toolkit
82
+
83
+ # Bun
84
+ bun add @tantainnovative/ndpr-toolkit
85
+
86
+ # npm
87
+ npm install @tantainnovative/ndpr-toolkit
88
+
89
+ # Yarn
90
+ yarn add @tantainnovative/ndpr-toolkit
79
91
  ```
80
92
 
81
93
  Add the stylesheet import once in your app entry so components render with default styles:
82
94
 
83
95
  ```ts
84
- // app/layout.tsx (Next.js App Router) or src/main.tsx (Vite/CRA)
96
+ // app/layout.tsx (Next.js App Router) or src/main.tsx (Vite/CRA/Bun)
85
97
  import "@tantainnovative/ndpr-toolkit/styles";
86
98
  ```
87
99
 
@@ -90,7 +102,11 @@ The stylesheet is opinionated but token-driven — override any `--ndpr-*` CSS c
90
102
  Install UI peer dependencies (only needed if you use the higher-level Radix-based components from `/presets`):
91
103
 
92
104
  ```bash
105
+ # pnpm
93
106
  pnpm add @radix-ui/react-switch @radix-ui/react-tabs @radix-ui/react-label @radix-ui/react-slot lucide-react tailwind-merge clsx class-variance-authority
107
+
108
+ # Bun
109
+ bun add @radix-ui/react-switch @radix-ui/react-tabs @radix-ui/react-label @radix-ui/react-slot lucide-react tailwind-merge clsx class-variance-authority
94
110
  ```
95
111
 
96
112
  Or scaffold instantly with the CLI:
@@ -1,7 +1,141 @@
1
- export declare function apiAdapter<T = unknown>(endpoint: string, options?: ApiAdapterOptions): StorageAdapter<T>;
1
+ /**
2
+ * Production-ready API storage adapter.
3
+ *
4
+ * Backward-compatible with the 3.5.x signature — `apiAdapter('/api/x')`
5
+ * still works exactly as before. New options are all opt-in.
6
+ *
7
+ * @example basic
8
+ * const adapter = apiAdapter<ConsentSettings>('/api/consent');
9
+ *
10
+ * @example with credentials and CSRF
11
+ * const adapter = apiAdapter<ConsentSettings>('/api/consent', {
12
+ * credentials: 'include',
13
+ * headers: () => ({
14
+ * 'X-CSRF-Token': document.querySelector<HTMLMetaElement>(
15
+ * 'meta[name="csrf-token"]'
16
+ * )?.content ?? '',
17
+ * }),
18
+ * });
19
+ *
20
+ * @example with retry + telemetry
21
+ * const adapter = apiAdapter<ConsentSettings>('/api/consent', {
22
+ * retry: { attempts: 2, baseDelayMs: 300 },
23
+ * onError: (ctx) => Sentry.captureException(ctx.error, { extra: ctx }),
24
+ * onSuccess: (ctx) => analytics.track('consent_saved', { method: ctx.method }),
25
+ * });
26
+ *
27
+ * @example with response unwrap
28
+ * const adapter = apiAdapter<ConsentSettings>('/api/consent', {
29
+ * // API returns { data: ConsentSettings, ok: true }
30
+ * unwrap: (raw) => (raw as { data: ConsentSettings }).data,
31
+ * });
32
+ */
33
+ export declare function apiAdapter<T = unknown>(endpoint: string, options?: ApiAdapterOptions<T>): StorageAdapter<T>;
2
34
 
3
- export declare interface ApiAdapterOptions {
4
- headers?: Record<string, string>;
35
+ declare interface ApiAdapterErrorContext<T = unknown> {
36
+ /** Which adapter operation triggered this — `load`, `save`, or `remove`. */
37
+ method: ApiAdapterMethod;
38
+ /** The endpoint URL that failed. */
39
+ endpoint: string;
40
+ /** Underlying error (for network failures / parse errors). */
41
+ error?: unknown;
42
+ /** Response object, if a response was received. */
43
+ response?: Response;
44
+ /** HTTP status code, if available. */
45
+ status?: number;
46
+ /** For `save`, the payload that failed to send. */
47
+ payload?: T;
48
+ /** Which retry attempt this is (0 = first try). Capped at `retry.attempts`. */
49
+ attempt: number;
50
+ }
51
+
52
+ declare type ApiAdapterMethod = 'load' | 'save' | 'remove';
53
+
54
+ export declare interface ApiAdapterOptions<T = unknown> {
55
+ /**
56
+ * Extra HTTP headers to send with every request. Useful for `Authorization`,
57
+ * `X-CSRF-Token`, `X-Requested-With`, etc.
58
+ *
59
+ * Can also be a function that returns headers, which lets you read a CSRF
60
+ * token from the DOM/cookie at request time rather than at adapter
61
+ * construction time.
62
+ */
63
+ headers?: Record<string, string> | (() => Record<string, string>);
64
+ /**
65
+ * Forwarded to fetch's `credentials` option. Defaults to `'same-origin'`
66
+ * (the browser default). Set to `'include'` for cross-origin endpoints
67
+ * that need cookies / auth.
68
+ */
69
+ credentials?: RequestCredentials;
70
+ /**
71
+ * HTTP method override for the load operation. Defaults to `'GET'`.
72
+ */
73
+ loadMethod?: 'GET' | 'POST';
74
+ /**
75
+ * HTTP method override for the save operation. Defaults to `'POST'`. Some
76
+ * REST APIs prefer `'PUT'` for upsert semantics.
77
+ */
78
+ saveMethod?: 'POST' | 'PUT' | 'PATCH';
79
+ /**
80
+ * Transform the raw JSON response into the expected `T`. Useful for APIs
81
+ * that wrap responses in `{ data: ... }` or similar envelopes. Called
82
+ * after `res.json()`. If omitted, the parsed JSON is used as-is.
83
+ */
84
+ unwrap?: (raw: unknown) => T | null;
85
+ /**
86
+ * Retry policy for failed requests. Defaults to no retries (preserves the
87
+ * pre-3.6.0 behaviour). When configured, applies to all three operations.
88
+ */
89
+ retry?: ApiAdapterRetryConfig;
90
+ /**
91
+ * Called when a request fails (after all retries exhausted). The adapter
92
+ * still returns a graceful null/void result so the consuming hook
93
+ * doesn't crash — this hook is for telemetry, toasts, or audit logging.
94
+ */
95
+ onError?: (ctx: ApiAdapterErrorContext<T>) => void;
96
+ /**
97
+ * Called when a request succeeds. Useful for cache invalidation,
98
+ * analytics, or syncing other state.
99
+ */
100
+ onSuccess?: (ctx: ApiAdapterSuccessContext<T>) => void;
101
+ /**
102
+ * Per-request fetch options to merge into every request. Use this for
103
+ * things `fetch` itself supports that aren't directly modelled above —
104
+ * `signal`, `mode`, `cache`, `redirect`, etc.
105
+ */
106
+ fetchInit?: Omit<RequestInit, 'method' | 'headers' | 'body' | 'credentials'>;
107
+ }
108
+
109
+ declare interface ApiAdapterRetryConfig {
110
+ /**
111
+ * Number of additional attempts after the initial request. Defaults to 0
112
+ * (no retries). e.g. `attempts: 2` means up to 3 total requests.
113
+ */
114
+ attempts?: number;
115
+ /**
116
+ * Base delay in ms between attempts. Defaults to 250ms. The actual delay
117
+ * uses exponential backoff: `baseDelayMs * 2^attempt`.
118
+ */
119
+ baseDelayMs?: number;
120
+ /**
121
+ * Predicate that decides whether to retry given the failure context. By
122
+ * default we retry on network errors and 5xx responses, but not on 4xx
123
+ * (those are client errors that won't fix themselves).
124
+ */
125
+ shouldRetry?: (ctx: ApiAdapterErrorContext<unknown>) => boolean;
126
+ }
127
+
128
+ declare interface ApiAdapterSuccessContext<T = unknown> {
129
+ /** Which adapter operation succeeded — `load`, `save`, or `remove`. */
130
+ method: ApiAdapterMethod;
131
+ /** The endpoint URL. */
132
+ endpoint: string;
133
+ /** Response object. */
134
+ response: Response;
135
+ /** For `load` operations, the parsed (and optionally unwrapped) data. */
136
+ data?: T;
137
+ /** For `save` operations, the payload that was sent. */
138
+ payload?: T;
5
139
  }
6
140
 
7
141
  export declare function composeAdapters<T = unknown>(primary: StorageAdapter<T>, ...secondaries: StorageAdapter<T>[]): StorageAdapter<T>;
@@ -1,7 +1,141 @@
1
- export declare function apiAdapter<T = unknown>(endpoint: string, options?: ApiAdapterOptions): StorageAdapter<T>;
1
+ /**
2
+ * Production-ready API storage adapter.
3
+ *
4
+ * Backward-compatible with the 3.5.x signature — `apiAdapter('/api/x')`
5
+ * still works exactly as before. New options are all opt-in.
6
+ *
7
+ * @example basic
8
+ * const adapter = apiAdapter<ConsentSettings>('/api/consent');
9
+ *
10
+ * @example with credentials and CSRF
11
+ * const adapter = apiAdapter<ConsentSettings>('/api/consent', {
12
+ * credentials: 'include',
13
+ * headers: () => ({
14
+ * 'X-CSRF-Token': document.querySelector<HTMLMetaElement>(
15
+ * 'meta[name="csrf-token"]'
16
+ * )?.content ?? '',
17
+ * }),
18
+ * });
19
+ *
20
+ * @example with retry + telemetry
21
+ * const adapter = apiAdapter<ConsentSettings>('/api/consent', {
22
+ * retry: { attempts: 2, baseDelayMs: 300 },
23
+ * onError: (ctx) => Sentry.captureException(ctx.error, { extra: ctx }),
24
+ * onSuccess: (ctx) => analytics.track('consent_saved', { method: ctx.method }),
25
+ * });
26
+ *
27
+ * @example with response unwrap
28
+ * const adapter = apiAdapter<ConsentSettings>('/api/consent', {
29
+ * // API returns { data: ConsentSettings, ok: true }
30
+ * unwrap: (raw) => (raw as { data: ConsentSettings }).data,
31
+ * });
32
+ */
33
+ export declare function apiAdapter<T = unknown>(endpoint: string, options?: ApiAdapterOptions<T>): StorageAdapter<T>;
2
34
 
3
- export declare interface ApiAdapterOptions {
4
- headers?: Record<string, string>;
35
+ declare interface ApiAdapterErrorContext<T = unknown> {
36
+ /** Which adapter operation triggered this — `load`, `save`, or `remove`. */
37
+ method: ApiAdapterMethod;
38
+ /** The endpoint URL that failed. */
39
+ endpoint: string;
40
+ /** Underlying error (for network failures / parse errors). */
41
+ error?: unknown;
42
+ /** Response object, if a response was received. */
43
+ response?: Response;
44
+ /** HTTP status code, if available. */
45
+ status?: number;
46
+ /** For `save`, the payload that failed to send. */
47
+ payload?: T;
48
+ /** Which retry attempt this is (0 = first try). Capped at `retry.attempts`. */
49
+ attempt: number;
50
+ }
51
+
52
+ declare type ApiAdapterMethod = 'load' | 'save' | 'remove';
53
+
54
+ export declare interface ApiAdapterOptions<T = unknown> {
55
+ /**
56
+ * Extra HTTP headers to send with every request. Useful for `Authorization`,
57
+ * `X-CSRF-Token`, `X-Requested-With`, etc.
58
+ *
59
+ * Can also be a function that returns headers, which lets you read a CSRF
60
+ * token from the DOM/cookie at request time rather than at adapter
61
+ * construction time.
62
+ */
63
+ headers?: Record<string, string> | (() => Record<string, string>);
64
+ /**
65
+ * Forwarded to fetch's `credentials` option. Defaults to `'same-origin'`
66
+ * (the browser default). Set to `'include'` for cross-origin endpoints
67
+ * that need cookies / auth.
68
+ */
69
+ credentials?: RequestCredentials;
70
+ /**
71
+ * HTTP method override for the load operation. Defaults to `'GET'`.
72
+ */
73
+ loadMethod?: 'GET' | 'POST';
74
+ /**
75
+ * HTTP method override for the save operation. Defaults to `'POST'`. Some
76
+ * REST APIs prefer `'PUT'` for upsert semantics.
77
+ */
78
+ saveMethod?: 'POST' | 'PUT' | 'PATCH';
79
+ /**
80
+ * Transform the raw JSON response into the expected `T`. Useful for APIs
81
+ * that wrap responses in `{ data: ... }` or similar envelopes. Called
82
+ * after `res.json()`. If omitted, the parsed JSON is used as-is.
83
+ */
84
+ unwrap?: (raw: unknown) => T | null;
85
+ /**
86
+ * Retry policy for failed requests. Defaults to no retries (preserves the
87
+ * pre-3.6.0 behaviour). When configured, applies to all three operations.
88
+ */
89
+ retry?: ApiAdapterRetryConfig;
90
+ /**
91
+ * Called when a request fails (after all retries exhausted). The adapter
92
+ * still returns a graceful null/void result so the consuming hook
93
+ * doesn't crash — this hook is for telemetry, toasts, or audit logging.
94
+ */
95
+ onError?: (ctx: ApiAdapterErrorContext<T>) => void;
96
+ /**
97
+ * Called when a request succeeds. Useful for cache invalidation,
98
+ * analytics, or syncing other state.
99
+ */
100
+ onSuccess?: (ctx: ApiAdapterSuccessContext<T>) => void;
101
+ /**
102
+ * Per-request fetch options to merge into every request. Use this for
103
+ * things `fetch` itself supports that aren't directly modelled above —
104
+ * `signal`, `mode`, `cache`, `redirect`, etc.
105
+ */
106
+ fetchInit?: Omit<RequestInit, 'method' | 'headers' | 'body' | 'credentials'>;
107
+ }
108
+
109
+ declare interface ApiAdapterRetryConfig {
110
+ /**
111
+ * Number of additional attempts after the initial request. Defaults to 0
112
+ * (no retries). e.g. `attempts: 2` means up to 3 total requests.
113
+ */
114
+ attempts?: number;
115
+ /**
116
+ * Base delay in ms between attempts. Defaults to 250ms. The actual delay
117
+ * uses exponential backoff: `baseDelayMs * 2^attempt`.
118
+ */
119
+ baseDelayMs?: number;
120
+ /**
121
+ * Predicate that decides whether to retry given the failure context. By
122
+ * default we retry on network errors and 5xx responses, but not on 4xx
123
+ * (those are client errors that won't fix themselves).
124
+ */
125
+ shouldRetry?: (ctx: ApiAdapterErrorContext<unknown>) => boolean;
126
+ }
127
+
128
+ declare interface ApiAdapterSuccessContext<T = unknown> {
129
+ /** Which adapter operation succeeded — `load`, `save`, or `remove`. */
130
+ method: ApiAdapterMethod;
131
+ /** The endpoint URL. */
132
+ endpoint: string;
133
+ /** Response object. */
134
+ response: Response;
135
+ /** For `load` operations, the parsed (and optionally unwrapped) data. */
136
+ data?: T;
137
+ /** For `save` operations, the payload that was sent. */
138
+ payload?: T;
5
139
  }
6
140
 
7
141
  export declare function composeAdapters<T = unknown>(primary: StorageAdapter<T>, ...secondaries: StorageAdapter<T>[]): StorageAdapter<T>;
package/dist/adapters.js CHANGED
@@ -1 +1 @@
1
- 'use strict';var chunk6NXXQYQL_js=require('./chunk-6NXXQYQL.js'),chunk7ZZO7GVB_js=require('./chunk-7ZZO7GVB.js'),chunkVWED6UTN_js=require('./chunk-VWED6UTN.js');require('./chunk-RFPLZDIO.js');Object.defineProperty(exports,"apiAdapter",{enumerable:true,get:function(){return chunk6NXXQYQL_js.a}});Object.defineProperty(exports,"composeAdapters",{enumerable:true,get:function(){return chunk6NXXQYQL_js.c}});Object.defineProperty(exports,"memoryAdapter",{enumerable:true,get:function(){return chunk6NXXQYQL_js.b}});Object.defineProperty(exports,"cookieAdapter",{enumerable:true,get:function(){return chunk7ZZO7GVB_js.b}});Object.defineProperty(exports,"sessionStorageAdapter",{enumerable:true,get:function(){return chunk7ZZO7GVB_js.a}});Object.defineProperty(exports,"localStorageAdapter",{enumerable:true,get:function(){return chunkVWED6UTN_js.a}});
1
+ 'use strict';var chunkROTLSZMV_js=require('./chunk-ROTLSZMV.js'),chunk7ZZO7GVB_js=require('./chunk-7ZZO7GVB.js'),chunkVWED6UTN_js=require('./chunk-VWED6UTN.js');require('./chunk-RFPLZDIO.js');Object.defineProperty(exports,"apiAdapter",{enumerable:true,get:function(){return chunkROTLSZMV_js.a}});Object.defineProperty(exports,"composeAdapters",{enumerable:true,get:function(){return chunkROTLSZMV_js.c}});Object.defineProperty(exports,"memoryAdapter",{enumerable:true,get:function(){return chunkROTLSZMV_js.b}});Object.defineProperty(exports,"cookieAdapter",{enumerable:true,get:function(){return chunk7ZZO7GVB_js.b}});Object.defineProperty(exports,"sessionStorageAdapter",{enumerable:true,get:function(){return chunk7ZZO7GVB_js.a}});Object.defineProperty(exports,"localStorageAdapter",{enumerable:true,get:function(){return chunkVWED6UTN_js.a}});
package/dist/adapters.mjs CHANGED
@@ -1 +1 @@
1
- export{a as apiAdapter,c as composeAdapters,b as memoryAdapter}from'./chunk-NBZUZYTB.mjs';export{b as cookieAdapter,a as sessionStorageAdapter}from'./chunk-UASG46LP.mjs';export{a as localStorageAdapter}from'./chunk-DBZSN4WP.mjs';import'./chunk-ZJYULEER.mjs';
1
+ export{a as apiAdapter,c as composeAdapters,b as memoryAdapter}from'./chunk-PL4XNCQA.mjs';export{b as cookieAdapter,a as sessionStorageAdapter}from'./chunk-UASG46LP.mjs';export{a as localStorageAdapter}from'./chunk-DBZSN4WP.mjs';import'./chunk-ZJYULEER.mjs';
@@ -0,0 +1 @@
1
+ import {a}from'./chunk-BFAX7JQA.mjs';import {jsx}from'react/jsx-runtime';var p=[{id:"essential",label:"Essential Cookies",description:"Required for basic site functionality. Cannot be disabled.",required:true,purpose:"Site operation"},{id:"analytics",label:"Analytics",description:"Help us understand how visitors use our site to improve the experience.",required:false,purpose:"Usage analytics"},{id:"marketing",label:"Marketing",description:"Used to deliver relevant advertisements and track campaign effectiveness.",required:false,purpose:"Targeted advertising"},{id:"preferences",label:"Preferences",description:"Remember your settings and preferences for a personalised experience.",required:false,purpose:"Personalisation"}],f=({extraOptions:a$1=[],options:t,adapter:n,position:o="bottom",classNames:l,unstyled:d,onSave:s,copy:e})=>{let m=t!=null?t:[...p,...a$1];return jsx(a,{options:m,onSave:i=>{n&&n.save(i),s==null||s(i);},position:o,classNames:l,unstyled:d,manageStorage:!n,title:e==null?void 0:e.title,description:e==null?void 0:e.description,acceptAllButtonText:e==null?void 0:e.acceptAll,rejectAllButtonText:e==null?void 0:e.rejectAll,customizeButtonText:e==null?void 0:e.customize,saveButtonText:e==null?void 0:e.save})};export{f as a};
@@ -0,0 +1 @@
1
+ import {d,b,a}from'./chunk-ZJYULEER.mjs';function M(e){return e.response?e.response.status>=500:true}function O(e){return new Promise(r=>setTimeout(r,e))}function $(e){return e?typeof e=="function"?e():e:{}}function q(e,r={}){var w,k,g;let{headers:n,credentials:d$1="same-origin",loadMethod:p="GET",saveMethod:l="POST",unwrap:A,retry:s,onError:h,onSuccess:i,fetchInit:x}=r,T=(w=s==null?void 0:s.attempts)!=null?w:0,C=(k=s==null?void 0:s.baseDelayMs)!=null?k:250,E=(g=s==null?void 0:s.shouldRetry)!=null?g:M,y=h!=null?h:(t=>{t.method!=="load"&&(t.response?console.warn(`[ndpr-toolkit] Failed to ${t.method==="save"?"save to":"delete from"} ${t.endpoint}: ${t.response.status}`):console.warn(`[ndpr-toolkit] Failed to ${t.method==="save"?"save to":"delete from"} ${t.endpoint}`));});function v(t,o,u){return d(this,null,function*(){for(let c=0;c<=T;c++){let a$1,S;try{a$1=yield fetch(e,b(a(a({},x),o),{headers:a(a({},$(n)),o.headers),credentials:d$1}));}catch(b){S=b;}if(a$1&&a$1.ok)return {ok:true,response:a$1};let R={method:t,endpoint:e,error:S,response:a$1,status:a$1==null?void 0:a$1.status,payload:u,attempt:c};if(c===T||!E(R))return y(R),{ok:false};yield O(C*Math.pow(2,c));}return {ok:false}})}return {load(){return d(this,null,function*(){let t=yield v("load",{method:p,headers:{}});if(!t.ok)return null;try{let o=yield t.response.json(),u=A?A(o):o;return i&&i({method:"load",endpoint:e,response:t.response,data:u!=null?u:void 0}),u}catch(o){return y({method:"load",endpoint:e,error:o,response:t.response,status:t.response.status,attempt:T}),null}})},save(t){return d(this,null,function*(){let o=yield v("save",{method:l,headers:{"Content-Type":"application/json"},body:JSON.stringify(t)},t);o.ok&&i&&i({method:"save",endpoint:e,response:o.response,payload:t});})},remove(){return d(this,null,function*(){let t=yield v("remove",{method:"DELETE",headers:{}});t.ok&&i&&i({method:"remove",endpoint:e,response:t.response});})}}}function F(e){let r=e!=null?e:null;return {load(){return r},save(n){r=n;},remove(){r=null;}}}function H(e,...r){return {load(){return e.load()},save(n){let d=e.save(n),p=()=>{for(let l of r)try{l.save(n);}catch(A){console.warn("[ndpr-toolkit] Secondary adapter save failed:",A);}};if(d instanceof Promise)return d.then(()=>p());p();},remove(){let n=e.remove(),d=()=>{for(let p of r)try{p.remove();}catch(l){console.warn("[ndpr-toolkit] Secondary adapter remove failed:",l);}};if(n instanceof Promise)return n.then(()=>d());d();}}}export{q as a,F as b,H as c};
@@ -0,0 +1 @@
1
+ 'use strict';var chunkRFPLZDIO_js=require('./chunk-RFPLZDIO.js');function M(e){return e.response?e.response.status>=500:true}function O(e){return new Promise(r=>setTimeout(r,e))}function $(e){return e?typeof e=="function"?e():e:{}}function q(e,r={}){var w,k,g;let{headers:n,credentials:d="same-origin",loadMethod:p="GET",saveMethod:l="POST",unwrap:A,retry:s,onError:h,onSuccess:i,fetchInit:x}=r,T=(w=s==null?void 0:s.attempts)!=null?w:0,C=(k=s==null?void 0:s.baseDelayMs)!=null?k:250,E=(g=s==null?void 0:s.shouldRetry)!=null?g:M,y=h!=null?h:(t=>{t.method!=="load"&&(t.response?console.warn(`[ndpr-toolkit] Failed to ${t.method==="save"?"save to":"delete from"} ${t.endpoint}: ${t.response.status}`):console.warn(`[ndpr-toolkit] Failed to ${t.method==="save"?"save to":"delete from"} ${t.endpoint}`));});function v(t,o,u){return chunkRFPLZDIO_js.d(this,null,function*(){for(let c=0;c<=T;c++){let a,S;try{a=yield fetch(e,chunkRFPLZDIO_js.b(chunkRFPLZDIO_js.a(chunkRFPLZDIO_js.a({},x),o),{headers:chunkRFPLZDIO_js.a(chunkRFPLZDIO_js.a({},$(n)),o.headers),credentials:d}));}catch(b){S=b;}if(a&&a.ok)return {ok:true,response:a};let R={method:t,endpoint:e,error:S,response:a,status:a==null?void 0:a.status,payload:u,attempt:c};if(c===T||!E(R))return y(R),{ok:false};yield O(C*Math.pow(2,c));}return {ok:false}})}return {load(){return chunkRFPLZDIO_js.d(this,null,function*(){let t=yield v("load",{method:p,headers:{}});if(!t.ok)return null;try{let o=yield t.response.json(),u=A?A(o):o;return i&&i({method:"load",endpoint:e,response:t.response,data:u!=null?u:void 0}),u}catch(o){return y({method:"load",endpoint:e,error:o,response:t.response,status:t.response.status,attempt:T}),null}})},save(t){return chunkRFPLZDIO_js.d(this,null,function*(){let o=yield v("save",{method:l,headers:{"Content-Type":"application/json"},body:JSON.stringify(t)},t);o.ok&&i&&i({method:"save",endpoint:e,response:o.response,payload:t});})},remove(){return chunkRFPLZDIO_js.d(this,null,function*(){let t=yield v("remove",{method:"DELETE",headers:{}});t.ok&&i&&i({method:"remove",endpoint:e,response:t.response});})}}}function F(e){let r=e!=null?e:null;return {load(){return r},save(n){r=n;},remove(){r=null;}}}function H(e,...r){return {load(){return e.load()},save(n){let d=e.save(n),p=()=>{for(let l of r)try{l.save(n);}catch(A){console.warn("[ndpr-toolkit] Secondary adapter save failed:",A);}};if(d instanceof Promise)return d.then(()=>p());p();},remove(){let n=e.remove(),d=()=>{for(let p of r)try{p.remove();}catch(l){console.warn("[ndpr-toolkit] Secondary adapter remove failed:",l);}};if(n instanceof Promise)return n.then(()=>d());d();}}}exports.a=q;exports.b=F;exports.c=H;
@@ -0,0 +1 @@
1
+ import {a}from'./chunk-BNHQFZHL.mjs';import {a as a$1}from'./chunk-ZJYULEER.mjs';import {jsx}from'react/jsx-runtime';var p=t=>jsx(a,a$1({},t));export{p as a};
@@ -0,0 +1 @@
1
+ 'use strict';var chunkW47OSMT6_js=require('./chunk-W47OSMT6.js'),chunkRFPLZDIO_js=require('./chunk-RFPLZDIO.js'),jsxRuntime=require('react/jsx-runtime');var S=[{id:"access",name:"Access My Data",description:"Request a copy of your personal data held by us",ndpaSection:"Section 34(1)(a)\u2013(b)",estimatedCompletionTime:30,requiresAdditionalInfo:false},{id:"rectification",name:"Correct My Data",description:"Request corrections to inaccurate personal data",ndpaSection:"Section 34(1)(c)",estimatedCompletionTime:30,requiresAdditionalInfo:true,additionalFields:[{id:"correction_details",label:"What data needs to be corrected?",type:"textarea",required:true,placeholder:"Please describe the inaccurate data and what the correct information should be"}]},{id:"erasure",name:"Delete My Data",description:"Request deletion of your personal data",ndpaSection:"Section 34(1)(d), Section 34(2)",estimatedCompletionTime:30,requiresAdditionalInfo:false},{id:"portability",name:"Export My Data",description:"Receive your data in a portable format",ndpaSection:"Section 38",estimatedCompletionTime:30,requiresAdditionalInfo:false},{id:"restrict",name:"Restrict Processing",description:"Request restriction of data processing",ndpaSection:"Section 34(1)(e)",estimatedCompletionTime:30,requiresAdditionalInfo:false},{id:"object",name:"Object to Processing",description:"Object to processing of your personal data",ndpaSection:"Section 36",estimatedCompletionTime:30,requiresAdditionalInfo:false},{id:"withdraw_consent",name:"Withdraw My Consent",description:"Withdraw consent previously given for processing",ndpaSection:"Section 35",estimatedCompletionTime:30,requiresAdditionalInfo:false}],g=({requestTypes:p=S,adapter:i,classNames:m,unstyled:f,onSubmit:R=()=>{},submitTo:s,submitOptions:e,onSubmitError:t})=>jsxRuntime.jsx(chunkW47OSMT6_js.a,{requestTypes:p,onSubmit:o=>chunkRFPLZDIO_js.d(null,null,function*(){var n,r;if(s){let y=typeof(e==null?void 0:e.headers)=="function"?e.headers():(n=e==null?void 0:e.headers)!=null?n:{};try{let a=yield fetch(s,{method:"POST",headers:chunkRFPLZDIO_js.a({"Content-Type":"application/json"},y),credentials:(r=e==null?void 0:e.credentials)!=null?r:"same-origin",body:JSON.stringify(o)});a.ok||t==null||t({response:a});}catch(a){t==null||t({error:a});}}else i&&i.save(o);R(o);}),classNames:m,unstyled:f});exports.a=g;
@@ -0,0 +1 @@
1
+ import {a}from'./chunk-XJO4DH3L.mjs';import {d,a as a$1}from'./chunk-ZJYULEER.mjs';import {jsx}from'react/jsx-runtime';var S=[{id:"access",name:"Access My Data",description:"Request a copy of your personal data held by us",ndpaSection:"Section 34(1)(a)\u2013(b)",estimatedCompletionTime:30,requiresAdditionalInfo:false},{id:"rectification",name:"Correct My Data",description:"Request corrections to inaccurate personal data",ndpaSection:"Section 34(1)(c)",estimatedCompletionTime:30,requiresAdditionalInfo:true,additionalFields:[{id:"correction_details",label:"What data needs to be corrected?",type:"textarea",required:true,placeholder:"Please describe the inaccurate data and what the correct information should be"}]},{id:"erasure",name:"Delete My Data",description:"Request deletion of your personal data",ndpaSection:"Section 34(1)(d), Section 34(2)",estimatedCompletionTime:30,requiresAdditionalInfo:false},{id:"portability",name:"Export My Data",description:"Receive your data in a portable format",ndpaSection:"Section 38",estimatedCompletionTime:30,requiresAdditionalInfo:false},{id:"restrict",name:"Restrict Processing",description:"Request restriction of data processing",ndpaSection:"Section 34(1)(e)",estimatedCompletionTime:30,requiresAdditionalInfo:false},{id:"object",name:"Object to Processing",description:"Object to processing of your personal data",ndpaSection:"Section 36",estimatedCompletionTime:30,requiresAdditionalInfo:false},{id:"withdraw_consent",name:"Withdraw My Consent",description:"Withdraw consent previously given for processing",ndpaSection:"Section 35",estimatedCompletionTime:30,requiresAdditionalInfo:false}],g=({requestTypes:p=S,adapter:i,classNames:m,unstyled:f,onSubmit:R=()=>{},submitTo:s,submitOptions:e,onSubmitError:t})=>jsx(a,{requestTypes:p,onSubmit:o=>d(null,null,function*(){var n,r;if(s){let y=typeof(e==null?void 0:e.headers)=="function"?e.headers():(n=e==null?void 0:e.headers)!=null?n:{};try{let a=yield fetch(s,{method:"POST",headers:a$1({"Content-Type":"application/json"},y),credentials:(r=e==null?void 0:e.credentials)!=null?r:"same-origin",body:JSON.stringify(o)});a.ok||t==null||t({response:a});}catch(a){t==null||t({error:a});}}else i&&i.save(o);R(o);}),classNames:m,unstyled:f});export{g as a};
@@ -0,0 +1 @@
1
+ 'use strict';var chunk732C2EVN_js=require('./chunk-732C2EVN.js'),jsxRuntime=require('react/jsx-runtime');var p=[{id:"essential",label:"Essential Cookies",description:"Required for basic site functionality. Cannot be disabled.",required:true,purpose:"Site operation"},{id:"analytics",label:"Analytics",description:"Help us understand how visitors use our site to improve the experience.",required:false,purpose:"Usage analytics"},{id:"marketing",label:"Marketing",description:"Used to deliver relevant advertisements and track campaign effectiveness.",required:false,purpose:"Targeted advertising"},{id:"preferences",label:"Preferences",description:"Remember your settings and preferences for a personalised experience.",required:false,purpose:"Personalisation"}],f=({extraOptions:a=[],options:t,adapter:n,position:o="bottom",classNames:l,unstyled:d,onSave:s,copy:e})=>{let m=t!=null?t:[...p,...a];return jsxRuntime.jsx(chunk732C2EVN_js.a,{options:m,onSave:i=>{n&&n.save(i),s==null||s(i);},position:o,classNames:l,unstyled:d,manageStorage:!n,title:e==null?void 0:e.title,description:e==null?void 0:e.description,acceptAllButtonText:e==null?void 0:e.acceptAll,rejectAllButtonText:e==null?void 0:e.rejectAll,customizeButtonText:e==null?void 0:e.customize,saveButtonText:e==null?void 0:e.save})};exports.a=f;
@@ -0,0 +1 @@
1
+ 'use strict';var chunkI2LMQWK3_js=require('./chunk-I2LMQWK3.js'),chunkRFPLZDIO_js=require('./chunk-RFPLZDIO.js'),jsxRuntime=require('react/jsx-runtime');var p=t=>jsxRuntime.jsx(chunkI2LMQWK3_js.a,chunkRFPLZDIO_js.a({},t));exports.a=p;
@@ -0,0 +1,139 @@
1
+ import React__default from 'react';
2
+
3
+ declare interface ConsentBannerClassNames {
4
+ root?: string;
5
+ container?: string;
6
+ title?: string;
7
+ description?: string;
8
+ optionsList?: string;
9
+ optionItem?: string;
10
+ optionCheckbox?: string;
11
+ optionLabel?: string;
12
+ optionDescription?: string;
13
+ buttonGroup?: string;
14
+ acceptButton?: string;
15
+ rejectButton?: string;
16
+ customizeButton?: string;
17
+ saveButton?: string;
18
+ customizePanel?: string;
19
+ selectAllButton?: string;
20
+ /** Alias for acceptButton */
21
+ primaryButton?: string;
22
+ /** Alias for rejectButton */
23
+ secondaryButton?: string;
24
+ }
25
+
26
+ /**
27
+ * Consent types aligned with NDPA 2023 Section 25-26
28
+ * Consent must be freely given, specific, informed, and unambiguous
29
+ */
30
+ /**
31
+ * Represents a consent option that can be presented to users
32
+ */
33
+ declare interface ConsentOption {
34
+ /** Unique identifier for the consent option */
35
+ id: string;
36
+ /** Display label for the consent option */
37
+ label: string;
38
+ /** Detailed description of what this consent option covers */
39
+ description: string;
40
+ /** Whether this consent option is required (cannot be declined) */
41
+ required: boolean;
42
+ /**
43
+ * The specific purpose for which data will be processed
44
+ * NDPA Section 25(2) requires consent to be specific to each purpose
45
+ */
46
+ purpose: string;
47
+ /**
48
+ * Default state of the consent option
49
+ * @default false
50
+ */
51
+ defaultValue?: boolean;
52
+ /**
53
+ * Categories of personal data covered by this consent option
54
+ */
55
+ dataCategories?: string[];
56
+ }
57
+
58
+ /**
59
+ * Represents the user's consent settings
60
+ */
61
+ declare interface ConsentSettings {
62
+ /** Map of consent option IDs to boolean values indicating consent status */
63
+ consents: Record<string, boolean>;
64
+ /** Timestamp when consent was last updated */
65
+ timestamp: number;
66
+ /** Version of the consent form that was accepted */
67
+ version: string;
68
+ /** Method used to collect consent (e.g., "banner", "settings", "api") */
69
+ method: string;
70
+ /** Whether the user has actively made a choice (as opposed to default settings) */
71
+ hasInteracted: boolean;
72
+ /**
73
+ * The lawful basis under which processing is conducted
74
+ * Required by NDPA Section 25(1)
75
+ */
76
+ lawfulBasis?: LawfulBasisType;
77
+ }
78
+
79
+ /**
80
+ * Lawful basis for processing personal data per NDPA Section 25(1)
81
+ */
82
+ declare type LawfulBasisType = 'consent' | 'contract' | 'legal_obligation' | 'vital_interests' | 'public_interest' | 'legitimate_interests';
83
+
84
+ export declare const NDPRConsent: React__default.FC<NDPRConsentProps>;
85
+
86
+ /**
87
+ * UX copy overrides for the NDPRConsent preset. Pass any subset to
88
+ * replace the default text without dropping to the lower-level
89
+ * `<ConsentBanner>` API. Strings you omit fall back to the toolkit
90
+ * defaults (which already cite NDPA Section 26).
91
+ *
92
+ * @example
93
+ * <NDPRConsent copy={{
94
+ * title: 'Cookie preferences',
95
+ * description: 'Acme uses cookies to keep you signed in and improve our store.',
96
+ * acceptAll: 'Allow all',
97
+ * rejectAll: 'Only essentials',
98
+ * }} />
99
+ */
100
+ export declare interface NDPRConsentCopy {
101
+ /** Banner heading. Default: "We Value Your Privacy" */
102
+ title?: string;
103
+ /** Body paragraph under the heading. Default cites NDPA Section 26. */
104
+ description?: string;
105
+ /** Primary CTA — accepts all categories. Default: "Accept All" */
106
+ acceptAll?: string;
107
+ /** Secondary CTA — rejects all non-essential categories. Default: "Reject All" */
108
+ rejectAll?: string;
109
+ /** Tertiary CTA — opens the per-category controls. Default: "Customize" */
110
+ customize?: string;
111
+ /** Submit button on the per-category panel. Default: "Save Preferences" */
112
+ save?: string;
113
+ }
114
+
115
+ export declare interface NDPRConsentProps {
116
+ extraOptions?: ConsentOption[];
117
+ options?: ConsentOption[];
118
+ adapter?: StorageAdapter<ConsentSettings>;
119
+ position?: 'top' | 'bottom' | 'center' | 'inline';
120
+ classNames?: ConsentBannerClassNames;
121
+ unstyled?: boolean;
122
+ onSave?: (settings: ConsentSettings) => void;
123
+ /**
124
+ * UX copy overrides — see {@link NDPRConsentCopy}. Lets you brand the
125
+ * banner without dropping to the lower-level `<ConsentBanner>` API.
126
+ */
127
+ copy?: NDPRConsentCopy;
128
+ }
129
+
130
+ declare interface StorageAdapter<T = unknown> {
131
+ /** Load persisted data. Called once on hook mount. */
132
+ load(): T | null | Promise<T | null>;
133
+ /** Persist data. Called on every state change. */
134
+ save(data: T): void | Promise<void>;
135
+ /** Clear persisted data. Called on reset. */
136
+ remove(): void | Promise<void>;
137
+ }
138
+
139
+ export { }