@preprio/toolkit 0.1.0-beta.1 → 0.1.0-beta.4

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
@@ -1,6 +1,6 @@
1
1
  # @preprio/toolkit
2
2
 
3
- A framework-agnostic TypeScript library that provides preview functionality, visual editing capabilities, and A/B testing for [Prepr CMS](https://prepr.io). Ships thin wrappers for Next.js, Astro, and SvelteKit on top of a vanilla core that runs anywhere.
3
+ A framework-agnostic TypeScript library that provides preview functionality, visual editing capabilities, and A/B testing for [Prepr CMS](https://prepr.io). Ships thin wrappers for Next.js, Nuxt, Astro, and SvelteKit on top of a vanilla core that runs anywhere.
4
4
 
5
5
  ## Quick Start
6
6
 
@@ -83,6 +83,7 @@ Per-framework requirements:
83
83
  | `@preprio/toolkit/nextjs` | `next` >= 13, `react` >= 17, `react-dom` >= 17 |
84
84
  | `@preprio/toolkit/astro` | none (`.astro` components compile in your own pipeline) |
85
85
  | `@preprio/toolkit/sveltekit` | `svelte` (only for the `.svelte` components) |
86
+ | `@preprio/toolkit/nuxt` | `vue` (only for the `.vue` components) |
86
87
 
87
88
  All peer dependencies are optional — installing without React or Next.js is fine as long as you only import the entry points you have dependencies for.
88
89
 
@@ -471,6 +472,57 @@ const preprHeaders = getPreprHeadersFromLocals(event.locals);
471
472
 
472
473
  See the runnable example at `examples/sveltekit` for the full source of truth.
473
474
 
475
+ ## Nuxt
476
+
477
+ `vue` is an optional peer dependency, needed only if you import the `.vue` components. Requires Nitro's Node.js preset (the Nuxt default).
478
+
479
+ **`server/middleware/prepr.ts`**
480
+
481
+ ```typescript
482
+ import { handlePreprRequest } from '@preprio/toolkit/nuxt';
483
+
484
+ export default defineEventHandler(event => {
485
+ handlePreprRequest(event, { preview: import.meta.dev });
486
+ });
487
+ ```
488
+
489
+ **`app/app.vue`** — resolve the toolbar props on the server, gated on the same flag:
490
+
491
+ ```vue
492
+ <script setup lang="ts">
493
+ import { getToolbarProps } from '@preprio/toolkit/nuxt';
494
+ import PreprToolbar from '@preprio/toolkit/nuxt/components/PreprToolbar';
495
+ import PreprTrackingPixel from '@preprio/toolkit/nuxt/components/PreprTrackingPixel';
496
+
497
+ const requestHeaders = useRequestHeaders();
498
+ const { data: toolbarProps } = await useAsyncData('prepr-toolbar', async () => {
499
+ if (!import.meta.dev) return null;
500
+ return getToolbarProps(
501
+ new Headers(requestHeaders as Record<string, string>),
502
+ useRuntimeConfig().public.preprGraphqlUrl
503
+ );
504
+ });
505
+ </script>
506
+
507
+ <template>
508
+ <NuxtPage />
509
+ <PreprTrackingPixel id="YOUR_ACCESS_TOKEN" />
510
+ <PreprToolbar v-if="toolbarProps" v-bind="toolbarProps" />
511
+ </template>
512
+ ```
513
+
514
+ The `.vue` components ship as source (compiled by your own Vite/Vue pipeline) and mount client-side via `onMounted`, so they never run during SSR.
515
+
516
+ The middleware folds the computed Prepr headers back onto the incoming request — read them via `useRequestHeaders()` in composables, or straight off the h3 event in server routes:
517
+
518
+ ```typescript
519
+ import { getPreprHeadersFromEvent } from '@preprio/toolkit/nuxt';
520
+
521
+ const preprHeaders = getPreprHeadersFromEvent(event);
522
+ ```
523
+
524
+ See the runnable example at `examples/nuxt` for the full source of truth.
525
+
474
526
  ## Any other framework
475
527
 
476
528
  The core is runtime-neutral: it works off a WHATWG `Request` and hands back the headers to forward to Prepr plus the cookies to persist. Adapting it to Express, Hono, Fastify, or a plain server is roughly twenty lines.
@@ -522,7 +574,7 @@ Serialize the server-computed props into the page — a `<script type="applicati
522
574
 
523
575
  Each wrapper exposes the same five helpers, differing only in how they reach the request headers.
524
576
 
525
- | Helper | Next.js | Astro / SvelteKit |
577
+ | Helper | Next.js | Astro / SvelteKit / Nuxt |
526
578
  | --- | --- | --- |
527
579
  | `getPreprUUID` | `await getPreprUUID()` | `getPreprUUID(headers)` |
528
580
  | `getActiveSegment` | `await getActiveSegment()` | `getActiveSegment(headers)` |
@@ -530,7 +582,7 @@ Each wrapper exposes the same five helpers, differing only in how they reach the
530
582
  | `getPreprHeaders` | `await getPreprHeaders()` | `getPreprHeaders(headers)` |
531
583
  | `getToolbarProps` | `await getToolbarProps(token)` | `await getToolbarProps(headers, token)` |
532
584
 
533
- The Next.js versions read via `next/headers` and are async. The Astro and SvelteKit versions take a standard `Headers` (`Astro.request.headers` or `event.request.headers`) and are sync, except `getToolbarProps`.
585
+ The Next.js versions read via `next/headers` and are async. The Astro, SvelteKit and Nuxt versions take a standard `Headers` (`Astro.request.headers`, `event.request.headers`, or one built from `useRequestHeaders()`) and are sync, except `getToolbarProps`.
534
586
 
535
587
  #### `getPreprUUID()`
536
588
 
@@ -895,6 +947,7 @@ Runnable examples live in the repository root:
895
947
  | `examples/nextjs` | App Router, middleware, Apollo, custom image loader |
896
948
  | `examples/astro` | Astro middleware and `.astro` components |
897
949
  | `examples/sveltekit` | `hooks.server.ts`, `+layout.server.ts`, `.svelte` components |
950
+ | `examples/nuxt` | `server/middleware`, `useAsyncData`, `.vue` components |
898
951
  | `examples/express` | Vanilla core — a hand-written adapter for an unsupported framework |
899
952
 
900
953
  ## Support
@@ -30,7 +30,7 @@ __export(astro_exports, {
30
30
  module.exports = __toCommonJS(astro_exports);
31
31
 
32
32
  // src/version.ts
33
- var VERSION = "0.1.0-beta.1";
33
+ var VERSION = "0.1.0-beta.4";
34
34
 
35
35
  // src/core/server.ts
36
36
  var PreprError = class extends Error {
@@ -172,6 +172,20 @@ var UTM_PARAMS = [
172
172
  "utm_content",
173
173
  "utm_campaign"
174
174
  ];
175
+ function serializeCookie(cookie) {
176
+ const parts = [
177
+ `${cookie.name}=${encodeURIComponent(cookie.value)}`,
178
+ `Max-Age=${cookie.maxAge}`,
179
+ `Path=${cookie.path}`
180
+ ];
181
+ if (cookie.sameSite) {
182
+ parts.push(`SameSite=${cookie.sameSite}`);
183
+ }
184
+ if (cookie.secure) {
185
+ parts.push("Secure");
186
+ }
187
+ return parts.join("; ");
188
+ }
175
189
  function parseCookies(request) {
176
190
  const cookies = /* @__PURE__ */ new Map();
177
191
  const header = request.headers.get("cookie");
@@ -190,7 +204,11 @@ function processPreprRequest(request, opts) {
190
204
  const requestHeaders = new Headers(request.headers);
191
205
  const responseCookies = [];
192
206
  const requestCookies = parseCookies(request);
193
- const searchParams = new URL(request.url).searchParams;
207
+ const url = new URL(request.url);
208
+ const searchParams = url.searchParams;
209
+ const forwardedProto = request.headers.get("x-forwarded-proto")?.split(",")[0]?.trim();
210
+ const isSecure = forwardedProto ? forwardedProto === "https" : url.protocol === "https:";
211
+ const crossSite = isSecure ? { sameSite: "None", secure: true } : {};
194
212
  for (const key of UTM_PARAMS) {
195
213
  const value = searchParams.get(key);
196
214
  if (value !== null) {
@@ -239,6 +257,15 @@ function processPreprRequest(request, opts) {
239
257
  }
240
258
  requestHeaders.set("Prepr-Preview-Bar", "true");
241
259
  const isLivePreview = searchParams.get(PARAM_HIDE_BAR) === "true";
260
+ if (!isLivePreview && !requestCookies.has(COOKIE_PREVIEW_MODE)) {
261
+ responseCookies.push({
262
+ name: COOKIE_PREVIEW_MODE,
263
+ value: "true",
264
+ maxAge: ONE_YEAR_SECONDS,
265
+ path: "/",
266
+ ...crossSite
267
+ });
268
+ }
242
269
  if (!isLivePreview) {
243
270
  const segmentCookie = requestCookies.get(COOKIE_SEGMENT);
244
271
  if (segmentCookie) {
@@ -257,7 +284,8 @@ function processPreprRequest(request, opts) {
257
284
  name: COOKIE_VARIANT,
258
285
  value: previewAb,
259
286
  maxAge: ONE_YEAR_SECONDS,
260
- path: "/"
287
+ path: "/",
288
+ ...crossSite
261
289
  });
262
290
  }
263
291
  }
@@ -269,7 +297,8 @@ function processPreprRequest(request, opts) {
269
297
  name: COOKIE_SEGMENT,
270
298
  value: previewSegment,
271
299
  maxAge: ONE_YEAR_SECONDS,
272
- path: "/"
300
+ path: "/",
301
+ ...crossSite
273
302
  });
274
303
  }
275
304
  }
@@ -284,12 +313,7 @@ async function onPreprRequest(context, next, options) {
284
313
  });
285
314
  const response = await next();
286
315
  for (const cookie of responseCookies) {
287
- const parts = [
288
- `${cookie.name}=${encodeURIComponent(cookie.value)}`,
289
- `Max-Age=${cookie.maxAge}`,
290
- `Path=${cookie.path}`
291
- ];
292
- response.headers.append("Set-Cookie", parts.join("; "));
316
+ response.headers.append("Set-Cookie", serializeCookie(cookie));
293
317
  }
294
318
  return response;
295
319
  }
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var VERSION = "0.1.0-beta.1";
2
+ var VERSION = "0.1.0-beta.4";
3
3
 
4
4
  // src/core/server.ts
5
5
  var PreprError = class extends Error {
@@ -141,6 +141,20 @@ var UTM_PARAMS = [
141
141
  "utm_content",
142
142
  "utm_campaign"
143
143
  ];
144
+ function serializeCookie(cookie) {
145
+ const parts = [
146
+ `${cookie.name}=${encodeURIComponent(cookie.value)}`,
147
+ `Max-Age=${cookie.maxAge}`,
148
+ `Path=${cookie.path}`
149
+ ];
150
+ if (cookie.sameSite) {
151
+ parts.push(`SameSite=${cookie.sameSite}`);
152
+ }
153
+ if (cookie.secure) {
154
+ parts.push("Secure");
155
+ }
156
+ return parts.join("; ");
157
+ }
144
158
  function parseCookies(request) {
145
159
  const cookies = /* @__PURE__ */ new Map();
146
160
  const header = request.headers.get("cookie");
@@ -159,7 +173,11 @@ function processPreprRequest(request, opts) {
159
173
  const requestHeaders = new Headers(request.headers);
160
174
  const responseCookies = [];
161
175
  const requestCookies = parseCookies(request);
162
- const searchParams = new URL(request.url).searchParams;
176
+ const url = new URL(request.url);
177
+ const searchParams = url.searchParams;
178
+ const forwardedProto = request.headers.get("x-forwarded-proto")?.split(",")[0]?.trim();
179
+ const isSecure = forwardedProto ? forwardedProto === "https" : url.protocol === "https:";
180
+ const crossSite = isSecure ? { sameSite: "None", secure: true } : {};
163
181
  for (const key of UTM_PARAMS) {
164
182
  const value = searchParams.get(key);
165
183
  if (value !== null) {
@@ -208,6 +226,15 @@ function processPreprRequest(request, opts) {
208
226
  }
209
227
  requestHeaders.set("Prepr-Preview-Bar", "true");
210
228
  const isLivePreview = searchParams.get(PARAM_HIDE_BAR) === "true";
229
+ if (!isLivePreview && !requestCookies.has(COOKIE_PREVIEW_MODE)) {
230
+ responseCookies.push({
231
+ name: COOKIE_PREVIEW_MODE,
232
+ value: "true",
233
+ maxAge: ONE_YEAR_SECONDS,
234
+ path: "/",
235
+ ...crossSite
236
+ });
237
+ }
211
238
  if (!isLivePreview) {
212
239
  const segmentCookie = requestCookies.get(COOKIE_SEGMENT);
213
240
  if (segmentCookie) {
@@ -226,7 +253,8 @@ function processPreprRequest(request, opts) {
226
253
  name: COOKIE_VARIANT,
227
254
  value: previewAb,
228
255
  maxAge: ONE_YEAR_SECONDS,
229
- path: "/"
256
+ path: "/",
257
+ ...crossSite
230
258
  });
231
259
  }
232
260
  }
@@ -238,7 +266,8 @@ function processPreprRequest(request, opts) {
238
266
  name: COOKIE_SEGMENT,
239
267
  value: previewSegment,
240
268
  maxAge: ONE_YEAR_SECONDS,
241
- path: "/"
269
+ path: "/",
270
+ ...crossSite
242
271
  });
243
272
  }
244
273
  }
@@ -253,12 +282,7 @@ async function onPreprRequest(context, next, options) {
253
282
  });
254
283
  const response = await next();
255
284
  for (const cookie of responseCookies) {
256
- const parts = [
257
- `${cookie.name}=${encodeURIComponent(cookie.value)}`,
258
- `Max-Age=${cookie.maxAge}`,
259
- `Path=${cookie.path}`
260
- ];
261
- response.headers.append("Set-Cookie", parts.join("; "));
285
+ response.headers.append("Set-Cookie", serializeCookie(cookie));
262
286
  }
263
287
  return response;
264
288
  }
@@ -1006,7 +1006,7 @@ var CLOSE_ICON_SVG = `<svg width="10" height="10" viewBox="0 0 10 10" fill="none
1006
1006
  </svg>`;
1007
1007
 
1008
1008
  // src/core/ui/toolbar.generated.ts
1009
- var TOOLBAR_CSS = ":host{--prepr-primary:#4338ca;--prepr-bg:#eef2ff;--prepr-text:#1f2937;--prepr-radius:8px;--prepr-shadow:0px 0px 40px 0px #1f29373d;--prepr-z-index:10000;--prepr-primary-50:#eef2ff;--prepr-primary-100:#e0e7ff;--prepr-primary-800:#3730a3;--prepr-indigo-600:#4f46e5;--prepr-indigo-50:#eef2ff;--prepr-indigo-700:#4338ca;--prepr-secondary-400:#fb923c;--prepr-secondary-500:#f97316;--prepr-gray-100:#f3f4f6;--prepr-gray-200:#e5e7eb;--prepr-gray-300:#d1d5db;--prepr-gray-400:#9ca3af;--prepr-gray-500:#6b7280;--prepr-gray-700:#374151;--prepr-gray-800:#1f2937;--prepr-gray-900:#111827;--prepr-shadow-sm:0px 0px 12px 0px #c3c3c3cc;color:var(--prepr-text);box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-size:14px;line-height:1.5}:host *,:host :before,:host :after,*,:before,:after{box-sizing:border-box}button{font-family:inherit;font-size:inherit;margin:0}.prepr-regular-text{font-size:.875rem;font-weight:500;line-height:1.5}.prepr-backdrop{z-index:calc(var(--prepr-z-index) - 1);opacity:0;pointer-events:none;background:#0000001a;transition:opacity .2s ease-in-out;position:fixed;top:0;bottom:0;left:0;right:0}.prepr-backdrop[data-open=true]{opacity:1;pointer-events:auto;animation:.2s ease-in-out prepr-fade-in}.prepr-container{z-index:var(--prepr-z-index);justify-content:flex-end;align-items:center;display:flex;position:fixed;top:calc(50% - 64px);right:0}.prepr-toggle-holder{padding:.5rem}.prepr-toggle{cursor:pointer;background:var(--prepr-primary);color:#fff;border:none;border-radius:9999px;justify-content:center;align-items:center;width:2.25rem;height:2.25rem;display:flex}.prepr-toggle svg{color:#fff}.prepr-panel{z-index:var(--prepr-z-index);opacity:0;pointer-events:none;justify-content:flex-end;padding:.75rem;transition:opacity .2s ease-in-out;display:flex;position:fixed;top:50%;right:0;transform:translateY(-50%)}.prepr-panel[data-open=true]{opacity:1;pointer-events:auto;animation:.2s ease-in-out prepr-fade-in}.prepr-content{box-shadow:var(--prepr-shadow);border-radius:var(--prepr-radius);background:var(--prepr-bg);flex-direction:column;gap:2.5rem;width:100%;padding:1.5rem;display:flex}@media (min-width:640px){.prepr-panel{margin-right:4rem;padding:0}.prepr-content{width:502px;padding:2.5rem}}.prepr-header{justify-content:space-between;align-items:center;gap:.5rem;display:flex}.prepr-header-left{align-items:flex-start;gap:.75rem;display:flex}.prepr-badge{background:var(--prepr-primary-100);height:1.5rem;color:var(--prepr-primary);border-radius:4px;align-items:center;padding:.25rem .75rem;font-size:.875rem;display:flex}.prepr-header-close{cursor:pointer;background:var(--prepr-primary-100);width:2rem;height:2rem;color:var(--prepr-gray-700);border:none;border-radius:6px;justify-content:center;align-items:center;display:flex}.prepr-section{flex-direction:column;gap:.5rem;display:flex}.prepr-section-label{color:var(--prepr-gray-400);font-size:.875rem}.prepr-row{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:.5rem 1.5rem;display:flex}.prepr-row-title{color:var(--prepr-gray-800);margin:0;font-size:1rem;font-weight:600}.prepr-radiogroup{border-radius:var(--prepr-radius);border:1px solid var(--prepr-gray-300);background:#fff;align-items:center;gap:.25rem;height:2.5rem;padding:.25rem;display:flex}.prepr-radiogroup[data-disabled=true]{cursor:not-allowed;opacity:.5}.prepr-radiogroup[data-disabled=true] .prepr-radio{pointer-events:none}.prepr-radio{cursor:pointer;white-space:nowrap;text-align:center;height:2rem;color:var(--prepr-gray-900);background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:.5rem 1.125rem;font-size:.875rem;font-weight:500;transition:all .2s ease-in-out;display:flex}.prepr-radio[data-checked=true]{background:var(--prepr-indigo-600);color:#fff;box-shadow:var(--prepr-shadow-sm)}.prepr-radio[data-checked=true][data-off=true]{background:var(--prepr-gray-800);color:#fff}.prepr-radio-variant{width:82px}.prepr-listbox{position:relative}.prepr-segment-button{border-radius:var(--prepr-radius);border:1px solid var(--prepr-gray-300);width:240px;height:2.5rem;color:var(--prepr-gray-500);cursor:pointer;background:#fff;flex-shrink:0;align-items:center;gap:.5rem;padding:0 .5rem;font-size:.875rem;font-weight:500;display:flex;overflow:hidden}@media (min-width:768px){.prepr-segment-button{padding:0 1rem}}.prepr-segment-button:disabled{cursor:not-allowed;background:var(--prepr-gray-200);color:var(--prepr-gray-400)}.prepr-segment-button[aria-expanded=true]{border-bottom-color:#fff;border-bottom-right-radius:0;border-bottom-left-radius:0}.prepr-segment-label{white-space:nowrap;text-overflow:ellipsis;text-align:left;width:100%;margin-right:auto;overflow:hidden}.prepr-segment-caret{color:var(--prepr-gray-800);display:flex}.prepr-options{z-index:calc(var(--prepr-z-index) + 1);border-left:1px solid var(--prepr-gray-300);border-right:1px solid var(--prepr-gray-300);border-bottom:1px solid var(--prepr-gray-300);-ms-overflow-style:none;scrollbar-width:none;background:#fff;border-radius:0 0 6px 6px;width:240px;max-height:300px;margin:0;padding:0;list-style:none;position:absolute;top:100%;left:0;overflow-y:auto;box-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a}.prepr-options::-webkit-scrollbar{display:none}.prepr-options[hidden]{display:none}.prepr-option{cursor:pointer;width:100%;color:var(--prepr-gray-900);background:#fff;align-items:center;padding:.5rem 1rem;font-size:.875rem;font-weight:500;display:flex}.prepr-option:hover{background:var(--prepr-gray-100)}.prepr-option[aria-selected=true]{background:var(--prepr-indigo-50);color:var(--prepr-indigo-700)}.prepr-option-label{white-space:nowrap;text-overflow:ellipsis;text-align:left;width:100%;margin-right:auto;overflow:hidden}.prepr-reset{background:var(--prepr-secondary-400);color:#fff;cursor:pointer;border:none;border-radius:6px;justify-content:center;align-items:center;gap:.5rem;width:100%;height:2.5rem;padding:.5rem .75rem;font-size:.875rem;font-weight:500;display:flex}.prepr-reset:hover:not(:disabled){background:var(--prepr-secondary-500)}.prepr-reset:disabled{background:var(--prepr-gray-400);color:var(--prepr-gray-500);cursor:not-allowed}@media (min-width:768px){.prepr-reset{width:108px}}.prepr-indicators{z-index:var(--prepr-z-index);gap:.75rem;display:flex;position:fixed;bottom:1.5rem;right:1.5rem}.prepr-status-pill{background:var(--prepr-primary);color:#fff;cursor:pointer;border:none;border-radius:9999px;align-items:center;gap:.5rem;padding:.5rem 1rem;font-size:.75rem;font-weight:500;transition:background-color .15s;display:flex;box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a}.prepr-status-pill:hover{background:var(--prepr-primary-800)}.prepr-status-pill[hidden]{display:none}.prepr-status-viewing{color:#fff9;font-size:10px}.prepr-status-segment{text-overflow:ellipsis;white-space:nowrap;max-width:120px;display:inline-block;overflow:hidden}.prepr-status-variant{text-overflow:ellipsis;white-space:nowrap;background:#fff3;border-radius:4px;max-width:80px;padding:0 .5rem;display:inline-block;overflow:hidden}.prepr-status-x{color:#fff;width:.75rem;height:.75rem}.prepr-close-edit-pill{background:var(--prepr-primary-50);color:var(--prepr-gray-800);cursor:pointer;border:none;border-radius:9999px;align-items:center;gap:.5rem;padding:.5rem 1rem;font-size:.75rem;font-weight:500;transition:background-color .15s;display:flex;box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a}.prepr-close-edit-pill:hover{background:var(--prepr-primary-100)}.prepr-close-edit-pill[hidden]{display:none}.prepr-close-edit-x{width:1rem;height:1rem;color:var(--prepr-gray-500);transition:color .15s}.prepr-close-edit-pill:hover .prepr-close-edit-x{color:var(--prepr-gray-700)}.prepr-tip{z-index:calc(var(--prepr-z-index) + 2);overflow-wrap:break-word;background:var(--prepr-gray-900);color:#fff;pointer-events:none;border-radius:6px;width:auto;max-width:min(420px,100vw - 16px);padding:.5rem .75rem;font-size:.75rem;font-weight:500;position:fixed;box-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a}.prepr-tip-arrow{border-left:6px solid #0000;border-right:6px solid #0000;border-top:6px solid var(--prepr-gray-900);width:0;height:0;position:absolute;top:100%;transform:translate(-50%)}[hidden]{display:none!important}@keyframes prepr-fade-in{0%{opacity:0}to{opacity:1}}";
1009
+ var TOOLBAR_CSS = ":host{--prepr-primary:#4338ca;--prepr-bg:#eef2ff;--prepr-text:#1f2937;--prepr-radius:8px;--prepr-shadow:0px 0px 40px 0px #1f29373d;--prepr-z-index:10000;--prepr-primary-50:#eef2ff;--prepr-primary-100:#e0e7ff;--prepr-primary-800:#3730a3;--prepr-indigo-600:#4f46e5;--prepr-indigo-50:#eef2ff;--prepr-indigo-700:#4338ca;--prepr-secondary-400:#fb923c;--prepr-secondary-500:#f97316;--prepr-gray-100:#f3f4f6;--prepr-gray-200:#e5e7eb;--prepr-gray-300:#d1d5db;--prepr-gray-400:#9ca3af;--prepr-gray-500:#6b7280;--prepr-gray-700:#374151;--prepr-gray-800:#1f2937;--prepr-gray-900:#111827;--prepr-shadow-sm:0px 0px 12px 0px #c3c3c3cc;color:var(--prepr-text);box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-size:14px;line-height:1.5}:host *,:host :before,:host :after,*,:before,:after{box-sizing:border-box}button{font-family:inherit;font-size:inherit;margin:0}.prepr-regular-text{font-size:.875rem;font-weight:500;line-height:1.5}.prepr-backdrop{z-index:calc(var(--prepr-z-index) - 1);opacity:0;pointer-events:none;background:#0000001a;transition:opacity .2s ease-in-out;position:fixed;top:0;bottom:0;left:0;right:0}.prepr-backdrop[data-open=true]{opacity:1;pointer-events:auto;animation:.2s ease-in-out prepr-fade-in}.prepr-container{z-index:var(--prepr-z-index);justify-content:flex-end;align-items:center;display:flex;position:fixed;top:calc(50% - 64px);right:0}.prepr-toggle-holder{padding:.5rem}.prepr-toggle{cursor:pointer;background:var(--prepr-primary);color:#fff;border:none;border-radius:9999px;justify-content:center;align-items:center;width:2.25rem;height:2.25rem;display:flex}.prepr-toggle svg{color:#fff}.prepr-panel{z-index:var(--prepr-z-index);opacity:0;pointer-events:none;justify-content:flex-end;padding:.75rem;transition:opacity .2s ease-in-out;display:flex;position:fixed;top:50%;right:0;transform:translateY(-50%)}.prepr-panel[data-open=true]{opacity:1;pointer-events:auto;animation:.2s ease-in-out prepr-fade-in}.prepr-content{box-shadow:var(--prepr-shadow);border-radius:var(--prepr-radius);background:var(--prepr-bg);flex-direction:column;gap:2.5rem;width:100%;padding:1.5rem;display:flex}@media (min-width:640px){.prepr-panel{margin-right:4rem;padding:0}.prepr-content{width:502px;padding:2.5rem}}.prepr-header{justify-content:space-between;align-items:center;gap:.5rem;display:flex}.prepr-header-left{align-items:flex-start;gap:.75rem;display:flex}.prepr-badge{background:var(--prepr-primary-100);height:1.5rem;color:var(--prepr-primary);border-radius:4px;align-items:center;padding:.25rem .75rem;font-size:.875rem;display:flex}.prepr-header-close{cursor:pointer;background:var(--prepr-primary-100);width:2rem;height:2rem;color:var(--prepr-gray-700);border:none;border-radius:6px;justify-content:center;align-items:center;display:flex}.prepr-section{flex-direction:column;gap:.5rem;display:flex}.prepr-section-label{color:var(--prepr-gray-400);font-size:.875rem}.prepr-row{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:.5rem 1.5rem;display:flex}.prepr-row-title{color:var(--prepr-gray-800);margin:0;font-size:1rem;font-weight:600}.prepr-radiogroup{border-radius:var(--prepr-radius);border:1px solid var(--prepr-gray-300);background:#fff;align-items:center;gap:.25rem;height:2.5rem;padding:.25rem;display:flex}.prepr-radiogroup[data-disabled=true]{cursor:not-allowed;opacity:.5}.prepr-radiogroup[data-disabled=true] .prepr-radio{pointer-events:none}.prepr-radio{cursor:pointer;white-space:nowrap;text-align:center;height:2rem;color:var(--prepr-gray-900);background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:.5rem 1.125rem;font-size:.875rem;font-weight:500;transition:all .2s ease-in-out;display:flex}.prepr-radio[data-checked=true]{background:var(--prepr-indigo-600);color:#fff;box-shadow:var(--prepr-shadow-sm)}.prepr-radio[data-checked=true][data-off=true]{background:var(--prepr-gray-800);color:#fff}.prepr-radio-variant{width:82px}.prepr-listbox{position:relative}.prepr-segment-button{border-radius:var(--prepr-radius);border:1px solid var(--prepr-gray-300);width:240px;height:2.5rem;color:var(--prepr-gray-500);cursor:pointer;background:#fff;flex-shrink:0;align-items:center;gap:.5rem;padding:0 .5rem;font-size:.875rem;font-weight:500;display:flex;overflow:hidden}@media (min-width:768px){.prepr-segment-button{padding:0 1rem}}.prepr-segment-button:disabled{cursor:not-allowed;background:var(--prepr-gray-200);color:var(--prepr-gray-400)}.prepr-segment-button[aria-expanded=true]{border-bottom-color:#fff;border-bottom-right-radius:0;border-bottom-left-radius:0}.prepr-segment-label{white-space:nowrap;text-overflow:ellipsis;text-align:left;width:100%;margin-right:auto;overflow:hidden}.prepr-segment-caret{color:var(--prepr-gray-800);display:flex}.prepr-options{z-index:calc(var(--prepr-z-index) + 1);border-left:1px solid var(--prepr-gray-300);border-right:1px solid var(--prepr-gray-300);border-bottom:1px solid var(--prepr-gray-300);-ms-overflow-style:none;scrollbar-width:none;background:#fff;border-radius:0 0 6px 6px;width:240px;max-height:300px;margin:0;padding:0;list-style:none;position:absolute;top:100%;left:0;overflow-y:auto;box-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a}.prepr-options::-webkit-scrollbar{display:none}.prepr-options[hidden]{display:none}.prepr-option-search{background:#fff;padding:.5rem;position:-webkit-sticky;position:sticky;top:0}.prepr-segment-search{border:1px solid var(--prepr-gray-300);border-radius:var(--prepr-radius);width:100%;height:2rem;font:inherit;color:var(--prepr-gray-900);box-sizing:border-box;background:#fff;padding:0 .5rem;font-size:.875rem}.prepr-segment-search::placeholder{color:var(--prepr-gray-400)}.prepr-segment-search:focus{border-color:var(--prepr-gray-400);outline:none}.prepr-option{cursor:pointer;width:100%;color:var(--prepr-gray-900);background:#fff;align-items:center;padding:.5rem 1rem;font-size:.875rem;font-weight:500;display:flex}.prepr-option:hover{background:var(--prepr-gray-100)}.prepr-option[aria-selected=true]{background:var(--prepr-indigo-50);color:var(--prepr-indigo-700)}.prepr-option-label{white-space:nowrap;text-overflow:ellipsis;text-align:left;width:100%;margin-right:auto;overflow:hidden}.prepr-reset{background:var(--prepr-secondary-400);color:#fff;cursor:pointer;border:none;border-radius:6px;justify-content:center;align-items:center;gap:.5rem;width:100%;height:2.5rem;padding:.5rem .75rem;font-size:.875rem;font-weight:500;display:flex}.prepr-reset:hover:not(:disabled){background:var(--prepr-secondary-500)}.prepr-reset:disabled{background:var(--prepr-gray-400);color:var(--prepr-gray-500);cursor:not-allowed}@media (min-width:768px){.prepr-reset{width:108px}}.prepr-indicators{z-index:var(--prepr-z-index);gap:.75rem;display:flex;position:fixed;bottom:1.5rem;right:1.5rem}.prepr-status-pill{background:var(--prepr-primary);color:#fff;cursor:pointer;border:none;border-radius:9999px;align-items:center;gap:.5rem;padding:.5rem 1rem;font-size:.75rem;font-weight:500;transition:background-color .15s;display:flex;box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a}.prepr-status-pill:hover{background:var(--prepr-primary-800)}.prepr-status-pill[hidden]{display:none}.prepr-status-viewing{color:#fff9;font-size:10px}.prepr-status-segment{text-overflow:ellipsis;white-space:nowrap;max-width:120px;display:inline-block;overflow:hidden}.prepr-status-variant{text-overflow:ellipsis;white-space:nowrap;background:#fff3;border-radius:4px;max-width:80px;padding:0 .5rem;display:inline-block;overflow:hidden}.prepr-status-x{color:#fff;width:.75rem;height:.75rem}.prepr-close-edit-pill{background:var(--prepr-primary-50);color:var(--prepr-gray-800);cursor:pointer;border:none;border-radius:9999px;align-items:center;gap:.5rem;padding:.5rem 1rem;font-size:.75rem;font-weight:500;transition:background-color .15s;display:flex;box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a}.prepr-close-edit-pill:hover{background:var(--prepr-primary-100)}.prepr-close-edit-pill[hidden]{display:none}.prepr-close-edit-x{width:1rem;height:1rem;color:var(--prepr-gray-500);transition:color .15s}.prepr-close-edit-pill:hover .prepr-close-edit-x{color:var(--prepr-gray-700)}.prepr-tip{z-index:calc(var(--prepr-z-index) + 2);overflow-wrap:break-word;background:var(--prepr-gray-900);color:#fff;pointer-events:none;border-radius:6px;width:auto;max-width:min(420px,100vw - 16px);padding:.5rem .75rem;font-size:.75rem;font-weight:500;position:fixed;box-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a}.prepr-tip-arrow{border-left:6px solid #0000;border-right:6px solid #0000;border-top:6px solid var(--prepr-gray-900);width:0;height:0;position:absolute;top:100%;transform:translate(-50%)}[hidden]{display:none!important}@keyframes prepr-fade-in{0%{opacity:0}to{opacity:1}}";
1010
1010
 
1011
1011
  // ../../node_modules/.pnpm/preact@10.29.7/node_modules/preact/jsx-runtime/dist/jsxRuntime.mjs
1012
1012
  var f2 = 0;
@@ -1095,10 +1095,13 @@ function SegmentListbox({
1095
1095
  state,
1096
1096
  t: t3,
1097
1097
  listboxOpen,
1098
+ segmentFilter,
1098
1099
  handlers
1099
1100
  }) {
1100
1101
  const segments = state.segments;
1101
1102
  const hasSegments = segments.length > 0;
1103
+ const filter = segmentFilter.trim().toLowerCase();
1104
+ const visibleSegments = filter ? segments.filter((s2) => s2.name.toLowerCase().includes(filter)) : segments;
1102
1105
  let label;
1103
1106
  if (!hasSegments) {
1104
1107
  label = t3("adaptiveContent.none");
@@ -1135,15 +1138,30 @@ function SegmentListbox({
1135
1138
  role: "listbox",
1136
1139
  hidden: !listboxOpen,
1137
1140
  onKeyDown: (e2) => handlers.onOptionsKeydown(e2),
1138
- children: segments.map((seg) => /* @__PURE__ */ u2(
1139
- SegmentOption,
1140
- {
1141
- seg,
1142
- selected: seg._id === state.selectedSegment,
1143
- onChoose: handlers.onChooseSegment
1144
- },
1145
- seg._id
1146
- ))
1141
+ children: [
1142
+ /* @__PURE__ */ u2("li", { class: "prepr-option-search", role: "none", children: /* @__PURE__ */ u2(
1143
+ "input",
1144
+ {
1145
+ type: "text",
1146
+ class: "prepr-segment-search",
1147
+ "data-prepr": "segment-search",
1148
+ placeholder: t3("adaptiveContent.searchSegments"),
1149
+ value: segmentFilter,
1150
+ onInput: (e2) => handlers.onSegmentFilterInput(
1151
+ e2.currentTarget.value
1152
+ )
1153
+ }
1154
+ ) }),
1155
+ visibleSegments.map((seg) => /* @__PURE__ */ u2(
1156
+ SegmentOption,
1157
+ {
1158
+ seg,
1159
+ selected: seg._id === state.selectedSegment,
1160
+ onChoose: handlers.onChooseSegment
1161
+ },
1162
+ seg._id
1163
+ ))
1164
+ ]
1147
1165
  }
1148
1166
  )
1149
1167
  ] });
@@ -1268,7 +1286,7 @@ function StatusPill({
1268
1286
  {
1269
1287
  class: "prepr-status-x",
1270
1288
  "data-prepr": "status-x",
1271
- hidden: state.selectedSegment === null,
1289
+ hidden: !state.previewMode || state.selectedSegment === null,
1272
1290
  children: /* @__PURE__ */ u2(RawSvg, { svg: XMARK_ICON_SVG })
1273
1291
  }
1274
1292
  )
@@ -1297,7 +1315,13 @@ function CloseEditPill({
1297
1315
  }
1298
1316
  );
1299
1317
  }
1300
- function Panel({ state, t: t3, listboxOpen, handlers }) {
1318
+ function Panel({
1319
+ state,
1320
+ t: t3,
1321
+ listboxOpen,
1322
+ segmentFilter,
1323
+ handlers
1324
+ }) {
1301
1325
  const hasPersonalization = state.selectedSegment !== null || state.selectedVariant !== null;
1302
1326
  return /* @__PURE__ */ u2(S, { children: [
1303
1327
  /* @__PURE__ */ u2(
@@ -1354,6 +1378,7 @@ function Panel({ state, t: t3, listboxOpen, handlers }) {
1354
1378
  state,
1355
1379
  t: t3,
1356
1380
  listboxOpen,
1381
+ segmentFilter,
1357
1382
  handlers
1358
1383
  }
1359
1384
  )
@@ -1411,6 +1436,7 @@ var PreprToolbarElement = class extends HTMLElementBase {
1411
1436
  this.t = (key) => key;
1412
1437
  this.unsubscribe = null;
1413
1438
  this.listboxOpen = false;
1439
+ this.segmentFilter = "";
1414
1440
  this.handlers = {
1415
1441
  onToggle: () => this.set({ toolbarOpen: !this.state.toolbarOpen }),
1416
1442
  onClose: () => this.set({ toolbarOpen: false }),
@@ -1441,6 +1467,10 @@ var PreprToolbarElement = class extends HTMLElementBase {
1441
1467
  onSegmentButtonKeydown: (e2) => this.onListboxKeydown(e2),
1442
1468
  onOptionsKeydown: (e2) => this.onListboxKeydown(e2),
1443
1469
  onChooseSegment: (id) => this.chooseSegment(id),
1470
+ onSegmentFilterInput: (value) => {
1471
+ this.segmentFilter = value;
1472
+ this.renderPanel(this.state);
1473
+ },
1444
1474
  onPreviewTooltipEnter: (el) => this.showTooltip(el),
1445
1475
  onPreviewTooltipLeave: () => this.hideTooltip()
1446
1476
  };
@@ -1489,6 +1519,7 @@ var PreprToolbarElement = class extends HTMLElementBase {
1489
1519
  state,
1490
1520
  t: this.t,
1491
1521
  listboxOpen: this.listboxOpen,
1522
+ segmentFilter: this.segmentFilter,
1492
1523
  handlers: this.handlers
1493
1524
  }),
1494
1525
  root
@@ -1554,6 +1585,8 @@ var PreprToolbarElement = class extends HTMLElementBase {
1554
1585
  onListboxKeydown(event) {
1555
1586
  const e2 = event;
1556
1587
  const options = this.optionEls();
1588
+ const inSearch = e2.target?.getAttribute?.("data-prepr") === "segment-search";
1589
+ if (inSearch && e2.key === " ") return;
1557
1590
  if (options.length === 0 && e2.key !== "Escape") return;
1558
1591
  switch (e2.key) {
1559
1592
  case "Enter":
@@ -1615,6 +1648,7 @@ var PreprToolbarElement = class extends HTMLElementBase {
1615
1648
  const button = this.segmentButton();
1616
1649
  if (button?.disabled) return;
1617
1650
  this.listboxOpen = open;
1651
+ this.segmentFilter = "";
1618
1652
  this.renderPanel(this.state);
1619
1653
  if (open) {
1620
1654
  const options = this.optionEls();
@@ -1625,11 +1659,17 @@ var PreprToolbarElement = class extends HTMLElementBase {
1625
1659
  options.forEach(
1626
1660
  (el, i2) => el.setAttribute("data-active", String(i2 === activeIndex))
1627
1661
  );
1628
- options[activeIndex]?.focus();
1662
+ this.searchInput()?.focus();
1629
1663
  }
1630
1664
  }
1665
+ searchInput() {
1666
+ return this.shadowRoot?.querySelector(
1667
+ '[data-prepr="segment-search"]'
1668
+ ) ?? null;
1669
+ }
1631
1670
  chooseSegment(id) {
1632
1671
  this.listboxOpen = false;
1672
+ this.segmentFilter = "";
1633
1673
  this.set({ selectedSegment: id });
1634
1674
  this.renderPanel(this.state);
1635
1675
  this.segmentButton()?.focus();
@@ -1664,16 +1704,34 @@ function getCookie(name) {
1664
1704
  }
1665
1705
  function setCookie(name, value, options = {}) {
1666
1706
  if (typeof document === "undefined") return;
1667
- const { maxAge, path = "/" } = options;
1707
+ const { maxAge, path = "/", sameSite, secure } = options;
1668
1708
  let cookie = `${name}=${encodeURIComponent(value)};path=${path}`;
1669
1709
  if (typeof maxAge === "number") {
1670
1710
  cookie += `;max-age=${maxAge}`;
1671
1711
  }
1712
+ if (sameSite) {
1713
+ cookie += `;samesite=${sameSite}`;
1714
+ }
1715
+ if (secure) {
1716
+ cookie += ";secure";
1717
+ }
1672
1718
  document.cookie = cookie;
1673
1719
  }
1674
- function removeCookie(name, path = "/") {
1720
+ function removeCookie(name, path = "/", options = {}) {
1675
1721
  if (typeof document === "undefined") return;
1676
- document.cookie = `${name}=;path=${path};expires=Thu, 01 Jan 1970 00:00:00 GMT`;
1722
+ const { sameSite, secure } = options;
1723
+ let cookie = `${name}=;path=${path};expires=Thu, 01 Jan 1970 00:00:00 GMT`;
1724
+ if (sameSite) {
1725
+ cookie += `;samesite=${sameSite}`;
1726
+ }
1727
+ if (secure) {
1728
+ cookie += ";secure";
1729
+ }
1730
+ document.cookie = cookie;
1731
+ }
1732
+ function crossSiteCookieOptions() {
1733
+ if (typeof window === "undefined" || !window.isSecureContext) return {};
1734
+ return { sameSite: "None", secure: true };
1677
1735
  }
1678
1736
 
1679
1737
  // src/core/locales/en.json
@@ -1692,6 +1750,7 @@ var en_default = {
1692
1750
  segment: "Segment",
1693
1751
  ABVariant: "A/B variant",
1694
1752
  chooseSegment: "Choose segment",
1753
+ searchSegments: "Search segments",
1695
1754
  none: "No segments",
1696
1755
  allOtherUsers: "All other users",
1697
1756
  offDescription: "View the site as a real user (use cookies)",
@@ -1720,6 +1779,7 @@ var nl_default = {
1720
1779
  segment: "Segment",
1721
1780
  ABVariant: "A/B-variant",
1722
1781
  chooseSegment: "Kies segment",
1782
+ searchSegments: "Zoek segmenten",
1723
1783
  none: "Geen segmenten",
1724
1784
  allOtherUsers: "Alle andere gebruikers",
1725
1785
  offDescription: "Bekijk de site als een echte gebruiker (gebruik cookies)",
@@ -1802,7 +1862,13 @@ function createIframeBridge(store) {
1802
1862
  }
1803
1863
 
1804
1864
  // src/core/toolbar-change-handler.ts
1805
- var COOKIE_OPTS = { maxAge: ONE_YEAR_SECONDS, path: "/" };
1865
+ function cookieOpts() {
1866
+ return {
1867
+ maxAge: ONE_YEAR_SECONDS,
1868
+ path: "/",
1869
+ ...crossSiteCookieOptions()
1870
+ };
1871
+ }
1806
1872
  function createChangeHandler(deps) {
1807
1873
  const { store, stega, syncAutoClean } = deps;
1808
1874
  function updateParam(name, value) {
@@ -1819,9 +1885,9 @@ function createChangeHandler(deps) {
1819
1885
  return function handleChange(before, after) {
1820
1886
  if (before.selectedSegment !== after.selectedSegment) {
1821
1887
  if (after.selectedSegment === null) {
1822
- removeCookie(COOKIE_SEGMENT);
1888
+ removeCookie(COOKIE_SEGMENT, "/", crossSiteCookieOptions());
1823
1889
  } else {
1824
- setCookie(COOKIE_SEGMENT, after.selectedSegment, COOKIE_OPTS);
1890
+ setCookie(COOKIE_SEGMENT, after.selectedSegment, cookieOpts());
1825
1891
  }
1826
1892
  updateParam(PARAM_SEGMENT, after.selectedSegment);
1827
1893
  sendPreprEvent("segment_changed", {
@@ -1830,9 +1896,9 @@ function createChangeHandler(deps) {
1830
1896
  }
1831
1897
  if (before.selectedVariant !== after.selectedVariant) {
1832
1898
  if (after.selectedVariant === null) {
1833
- removeCookie(COOKIE_VARIANT);
1899
+ removeCookie(COOKIE_VARIANT, "/", crossSiteCookieOptions());
1834
1900
  } else {
1835
- setCookie(COOKIE_VARIANT, after.selectedVariant, COOKIE_OPTS);
1901
+ setCookie(COOKIE_VARIANT, after.selectedVariant, cookieOpts());
1836
1902
  }
1837
1903
  updateParam(PARAM_VARIANT, after.selectedVariant);
1838
1904
  sendPreprEvent("variant_changed", {
@@ -1858,14 +1924,23 @@ function createChangeHandler(deps) {
1858
1924
  if (Object.keys(coupled).length > 0) {
1859
1925
  store.set(coupled);
1860
1926
  }
1861
- setCookie(COOKIE_PREVIEW_MODE, String(after.previewMode), COOKIE_OPTS);
1862
- setCookie(COOKIE_TOOLBAR_OPEN, "false", COOKIE_OPTS);
1927
+ setCookie(COOKIE_PREVIEW_MODE, String(after.previewMode), cookieOpts());
1928
+ setCookie(COOKIE_TOOLBAR_OPEN, "false", cookieOpts());
1863
1929
  sendPreprEvent("preview_mode_toggled", { previewMode: after.previewMode });
1864
1930
  syncAutoClean(after.previewMode);
1865
- deps.reload();
1931
+ const [path, existing] = splitPath(deps.currentPath());
1932
+ const params = new URLSearchParams(existing);
1933
+ if (params.has(PARAM_SEGMENT) || params.has(PARAM_VARIANT)) {
1934
+ params.delete(PARAM_SEGMENT);
1935
+ params.delete(PARAM_VARIANT);
1936
+ const query = params.toString();
1937
+ deps.navigate(query ? `${path}?${query}` : path);
1938
+ } else {
1939
+ deps.reload();
1940
+ }
1866
1941
  }
1867
1942
  if (before.toolbarOpen !== after.toolbarOpen) {
1868
- setCookie(COOKIE_TOOLBAR_OPEN, String(after.toolbarOpen), COOKIE_OPTS);
1943
+ setCookie(COOKIE_TOOLBAR_OPEN, String(after.toolbarOpen), cookieOpts());
1869
1944
  }
1870
1945
  };
1871
1946
  }