@preprio/toolkit 0.1.0-beta.5 → 0.2.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +99 -14
  2. package/dist/astro/index.cjs +37 -15
  3. package/dist/astro/index.d.cts +38 -8
  4. package/dist/astro/index.d.ts +38 -8
  5. package/dist/astro/index.js +37 -15
  6. package/dist/{chunk-NLQIRN5O.js → chunk-6OOGNVJ4.js} +83 -41
  7. package/dist/index.cjs +102 -55
  8. package/dist/index.d.cts +59 -16
  9. package/dist/index.d.ts +59 -16
  10. package/dist/index.js +22 -16
  11. package/dist/nextjs/components.cjs +87 -42
  12. package/dist/nextjs/components.d.cts +3 -3
  13. package/dist/nextjs/components.d.ts +3 -3
  14. package/dist/nextjs/components.js +8 -4
  15. package/dist/nextjs/image-loader.cjs +17 -94
  16. package/dist/nextjs/image-loader.d.cts +8 -4
  17. package/dist/nextjs/image-loader.d.ts +8 -4
  18. package/dist/nextjs/image-loader.js +17 -94
  19. package/dist/nextjs/index.cjs +37 -15
  20. package/dist/nextjs/index.d.cts +46 -16
  21. package/dist/nextjs/index.d.ts +46 -16
  22. package/dist/nextjs/index.js +37 -15
  23. package/dist/nuxt/index.cjs +37 -15
  24. package/dist/nuxt/index.d.cts +62 -32
  25. package/dist/nuxt/index.d.ts +62 -32
  26. package/dist/nuxt/index.js +37 -15
  27. package/dist/{pixel-DVSJQC0K.d.cts → pixel-763p7m6A.d.cts} +64 -1
  28. package/dist/{pixel-DVSJQC0K.d.ts → pixel-763p7m6A.d.ts} +64 -1
  29. package/dist/sveltekit/index.cjs +37 -15
  30. package/dist/sveltekit/index.d.cts +62 -32
  31. package/dist/sveltekit/index.d.ts +62 -32
  32. package/dist/sveltekit/index.js +37 -15
  33. package/package.json +1 -1
  34. package/src/astro/PreprToolbar.astro +16 -10
  35. package/src/nuxt/PreprToolbar.vue +10 -8
  36. package/src/sveltekit/PreprToolbar.svelte +11 -4
package/README.md CHANGED
@@ -292,7 +292,11 @@ export default function RootLayout({ children }: { children: ReactNode }) {
292
292
 
293
293
  ### API Integration
294
294
 
295
- Use `getPreprHeaders()` to forward Prepr's personalization context to your own CMS or GraphQL fetches. Without these headers Prepr cannot resolve segments or A/B variants for the request.
295
+ Use `getPreprHeaders()` to forward the request's Prepr personalization context to your Prepr GraphQL fetches. Prepr resolves segments and A/B variants from these headers, so a query sent without them always returns the default, unpersonalized content.
296
+
297
+ The headers are set by the middleware, so they are only present on routes its matcher covers — off-matcher requests yield an empty object.
298
+
299
+ The signature differs per framework: `getPreprHeaders()` is async and zero-argument on Next.js (it reads `headers()` for you), while the Astro, SvelteKit, and Nuxt builds are synchronous and take the request's `Headers`.
296
300
 
297
301
  #### With the Fetch API
298
302
 
@@ -364,7 +368,15 @@ export default {
364
368
  };
365
369
  ```
366
370
 
367
- The loader only rewrites URLs on `*.stream.prepr.io` and `*.b-cdn.net`; anything else passes through untouched. Existing crop and quality transforms in the URL survive the resize.
371
+ The API emits each asset's real dimensions, so a Prepr URL renders as-is:
372
+
373
+ ```
374
+ https://393qibegr87z.b-cdn.net/w_800,h_600,fx_75,fy_67/b7ijwafd586-photo.jpg
375
+ ```
376
+
377
+ The loader rewrites `w_` to the width Next asks for and scales `h_` alongside it, holding the emitted ratio — for a cropped asset that ratio is the authored crop box, so the crop survives the resize. Focal points (`fx`/`fy`) are percentages and carry through unchanged, as does any other option in the segment. Quality is not a CDN option and is ignored.
378
+
379
+ URLs with no transform segment pass through untouched — set `unoptimized` on those `<Image>`s.
368
380
 
369
381
  See the runnable example at `examples/nextjs` for the full source of truth.
370
382
 
@@ -558,11 +570,11 @@ Forward the headers on your GraphQL fetch with `getPreprHeadersFromHeaders(reque
558
570
  **Client** — mount the toolbar imperatively:
559
571
 
560
572
  ```javascript
561
- import { createPreprToolbar, loadTrackingPixel } from '@preprio/toolkit';
573
+ import { createPreprPreview, loadTrackingPixel } from '@preprio/toolkit';
562
574
 
563
575
  loadTrackingPixel('YOUR_ACCESS_TOKEN');
564
576
 
565
- const controller = createPreprToolbar({ props: toolbarProps });
577
+ const controller = createPreprPreview({ props: toolbarProps });
566
578
  // later: controller.destroy()
567
579
  ```
568
580
 
@@ -695,12 +707,12 @@ const { requestHeaders, responseCookies } = processPreprRequest(request, { previ
695
707
  // responseCookies: CookieSpec[] — { name, value, maxAge, path }
696
708
  ```
697
709
 
698
- #### `createPreprToolbar(options)`
710
+ #### `createPreprPreview(options)`
699
711
 
700
- Mounts the toolbar and returns a controller.
712
+ Starts the preview runtime — optionally mounting the toolbar — and returns a controller.
701
713
 
702
714
  ```typescript
703
- const controller = createPreprToolbar({
715
+ const controller = createPreprPreview({
704
716
  props: toolbarProps,
705
717
  options: { debug: true, locale: 'nl' },
706
718
  navigation: {
@@ -715,13 +727,15 @@ controller.destroy();
715
727
 
716
728
  | Option | Type | Description |
717
729
  | --- | --- | --- |
718
- | `props` | `PreprToolbarProps` | Required. From `getToolbarProps`. |
719
- | `options` | `PreprToolbarOptions` | `{ debug?, locale? }`. |
730
+ | `props` | `PreprToolbarProps` | From `getToolbarProps`. Optional — a headless preview has no segment list to pass. |
731
+ | `options` | `PreprPreviewOptions` | `{ debug?, locale?, features?, ui?, allowedEditorOrigins? }`. |
720
732
  | `navigation` | `PreprNavigationAdapter` | How segment/variant switches navigate. |
721
733
 
722
734
  The navigation adapter is optional. Omit it and the toolbar uses `window.location.assign` — a full page load. Supply one to integrate with a client-side router; its `reload` member is optional too, and runs after a preview-mode toggle (defaulting to `window.location.reload()`). The Next.js and SvelteKit wrappers wire all of this up for you.
723
735
 
724
- `createPreprToolbar` is a no-op outside a browser (no `window`/`document`) and returns a controller whose `destroy()` does nothing, so it is safe to call during SSR. It also skips mounting when `prepr_hide_bar=true` is present, which is how the Prepr live-preview iframe suppresses the bar.
736
+ `createPreprPreview` is a no-op outside a browser (no `window`/`document`) and returns a controller whose `destroy()` does nothing, so it is safe to call during SSR.
737
+
738
+ Chrome is skipped — while every side effect stays wired — when any of these hold: `ui: false`, `?prepr_hide_bar=true` (how the Prepr live-preview iframe suppresses the bar), or running inside the editor's iframe. See [Headless preview](#headless-preview-no-toolbar-ui).
725
739
 
726
740
  #### `loadTrackingPixel(id, config?)`
727
741
 
@@ -798,18 +812,89 @@ createPreprMiddleware(request, response, { preview: true });
798
812
  | Option | Type | Description |
799
813
  | --- | --- | --- |
800
814
  | `preview` | `boolean` | Enables preview mode. The only gate — resolve it from your own environment signal. |
815
+ | `features` | `PreprFeatures` | Disable features app-wide. See [Feature flags](#feature-flags). |
801
816
  | `version` | `string` | Override the version reported in the `Prepr-Package` header. Mainly for tests. |
802
817
 
803
- ### Toolbar Options
818
+ ### Preview Options
804
819
 
805
820
  ```typescript
806
- createPreprToolbar({ props, options: { debug: true, locale: 'nl' } });
821
+ createPreprPreview({ props, options: { debug: true, locale: 'nl' } });
807
822
  ```
808
823
 
809
824
  | Option | Type | Default | Description |
810
825
  | --- | --- | --- | --- |
811
826
  | `debug` | `boolean` | `false` | Enable debug logging. |
812
827
  | `locale` | `'en' \| 'nl'` | auto-detected | UI language. Falls back to the first supported browser language, then `en`. |
828
+ | `features` | `PreprFeatures` | all enabled | Which features run. See [Feature flags](#feature-flags). |
829
+ | `ui` | `boolean` | `true` | Mount the visible toolbar. See [Headless preview](#headless-preview-no-toolbar-ui). |
830
+ | `allowedEditorOrigins` | `string[]` | `*.prepr.io` | Exact editor origins allowed to drive this preview, for self-hosted editors. Replaces the wildcard. |
831
+
832
+ `features` and `ui` are independent: `features` decides *what runs*, `ui` decides *whether the toolbar is visible*.
833
+
834
+ ### Headless preview (no toolbar UI)
835
+
836
+ `ui: false` keeps every non-visual side effect wired — click-to-edit, the editor bridge, scroll restore, cookies and headers — while rendering no chrome of its own. That is how a site gets live editing, or editor scroll restore, alongside its own UI instead of the Prepr bar.
837
+
838
+ ```typescript
839
+ // Live editing, no bar:
840
+ const preview = createPreprPreview({
841
+ options: { ui: false, features: { editMode: true } },
842
+ });
843
+
844
+ preview.destroy();
845
+ ```
846
+
847
+ `props` is optional here — a headless preview that only wants click-to-edit or scroll restore has no segment list to pass. For scroll restore and nothing else:
848
+
849
+ ```typescript
850
+ createPreprPreview({
851
+ options: {
852
+ ui: false,
853
+ features: { segments: false, abTesting: false, editMode: false },
854
+ },
855
+ });
856
+ ```
857
+
858
+ `?prepr_hide_bar=true` and the editor's own iframe both imply `ui: false`. Outside an iframe the bridge is a no-op, so mounting unconditionally is safe. Call `createPreprPreview` **once per page** — two calls start two bridges and announce the preview twice.
859
+
860
+ ### Feature flags
861
+
862
+ Preview mode is the master switch. Within it, each feature can be turned off with a `features` object. Everything is **on by default** — omit it and nothing changes.
863
+
864
+ ```typescript
865
+ import type { PreprFeatures } from '@preprio/toolkit';
866
+
867
+ export const preprFeatures: PreprFeatures = {
868
+ segments: false, // or { enabled: false }
869
+ abTesting: true,
870
+ editMode: { enabled: false },
871
+ };
872
+ ```
873
+
874
+ | Feature | Off means |
875
+ | --- | --- |
876
+ | `segments` | No segment picker. No `Prepr-Segments` header, from cookie **or** `?prepr_preview_segment`. No segment cookie written. The `_Segments` API call is skipped, saving a round-trip per page. |
877
+ | `abTesting` | No A/B control or variant badge. No `Prepr-ABtesting` header, from cookie **or** `?prepr_preview_ab`. No variant cookie written. |
878
+ | `editMode` | No Edit mode control, no click-to-edit overlay, no close-edit pill. |
879
+
880
+ Pass the **same object to both sides**. Each enforces its own half, so a feature disabled in only one place is still half-on — the UI would hide a control the middleware still honours:
881
+
882
+ ```typescript
883
+ // server
884
+ createPreprMiddleware(request, { preview: true, features: preprFeatures });
885
+ const props = await getToolbarProps(graphqlUrl, preprFeatures);
886
+
887
+ // client
888
+ <PreprToolbar {...props} options={{ features: preprFeatures }} />
889
+ ```
890
+
891
+ Every framework's `getToolbarProps` takes `features` as its last argument, and every `<PreprToolbar>` accepts an `options` prop. Keeping the object in one module both sides import is the simplest way to stay in sync.
892
+
893
+ Disabling a feature also stops its `segment_changed` / `variant_changed` events, and the Reset button ignores it — state the user cannot see is never rewritten.
894
+
895
+ **`editMode` does not disable the Prepr visual editor.** It governs your site's own click-to-edit affordance. When the Prepr editor frames your site it drives edit mode over its own `postMessage` handshake, which keeps working regardless — that is the CMS operating inside its own iframe, not an affordance offered to your visitors. Treat `editMode: false` as a UI choice, never as a security control.
896
+
897
+ Feature flags govern the toolbar and the middleware. The toolbar-free scroll-sync entry point carries no personalization state, so there is nothing there to disable.
813
898
 
814
899
  ### Pixel Options
815
900
 
@@ -852,7 +937,7 @@ Because the toolbar renders inside a shadow root, none of your own page CSS —
852
937
  The toolbar UI ships with English and Dutch. Pass `locale` explicitly, or omit it to auto-detect from the browser and fall back to `en`:
853
938
 
854
939
  ```typescript
855
- createPreprToolbar({ props, options: { locale: 'nl' } });
940
+ createPreprPreview({ props, options: { locale: 'nl' } });
856
941
  ```
857
942
 
858
943
  ## Troubleshooting
@@ -904,7 +989,7 @@ Codes: `INVALID_TOKEN`, `MISSING_TOKEN`, `HTTP_ERROR`, `FETCH_ERROR`, `INVALID_R
904
989
  ### Debug Mode
905
990
 
906
991
  ```typescript
907
- createPreprToolbar({ props, options: { debug: true } });
992
+ createPreprPreview({ props, options: { debug: true } });
908
993
  ```
909
994
 
910
995
  ## How It Works
@@ -30,7 +30,25 @@ __export(astro_exports, {
30
30
  module.exports = __toCommonJS(astro_exports);
31
31
 
32
32
  // src/version.ts
33
- var VERSION = "0.1.0-beta.5";
33
+ var VERSION = "0.2.0-beta.2";
34
+
35
+ // src/core/features.ts
36
+ function isEnabled(config) {
37
+ if (typeof config === "boolean") return config;
38
+ return config?.enabled ?? true;
39
+ }
40
+ function resolveFeatures(features) {
41
+ return {
42
+ segments: isEnabled(features?.segments),
43
+ abTesting: isEnabled(features?.abTesting),
44
+ editMode: isEnabled(features?.editMode)
45
+ };
46
+ }
47
+ var ALL_FEATURES_ENABLED = Object.freeze({
48
+ segments: true,
49
+ abTesting: true,
50
+ editMode: true
51
+ });
34
52
 
35
53
  // src/core/server.ts
36
54
  var PreprError = class extends Error {
@@ -138,16 +156,19 @@ async function getPreprEnvironmentSegments(token) {
138
156
  );
139
157
  }
140
158
  }
141
- async function getToolbarPropsFromHeaders(headers, token) {
159
+ async function getToolbarPropsFromHeaders(headers, token, features) {
160
+ const resolved = resolveFeatures(features);
142
161
  let segments = [];
143
- try {
144
- segments = await getPreprEnvironmentSegments(token);
145
- } catch {
146
- segments = [];
162
+ if (resolved.segments) {
163
+ try {
164
+ segments = await getPreprEnvironmentSegments(token);
165
+ } catch {
166
+ segments = [];
167
+ }
147
168
  }
148
169
  return {
149
- activeSegment: getActiveSegmentFromHeaders(headers),
150
- activeVariant: getActiveVariantFromHeaders(headers),
170
+ activeSegment: resolved.segments ? getActiveSegmentFromHeaders(headers) : null,
171
+ activeVariant: resolved.abTesting ? getActiveVariantFromHeaders(headers) : null,
151
172
  segments,
152
173
  data: segments
153
174
  };
@@ -247,7 +268,8 @@ function processPreprRequest(request, opts) {
247
268
  if (!opts?.preview) {
248
269
  return { requestHeaders, responseCookies };
249
270
  }
250
- const hasPreviewParams = searchParams.has(PARAM_SEGMENT) || searchParams.has(PARAM_VARIANT);
271
+ const features = resolveFeatures(opts?.features);
272
+ const hasPreviewParams = features.segments && searchParams.has(PARAM_SEGMENT) || features.abTesting && searchParams.has(PARAM_VARIANT);
251
273
  if (!hasPreviewParams) {
252
274
  const previewModeCookie = requestCookies.get(COOKIE_PREVIEW_MODE);
253
275
  const previewModeEnabled = previewModeCookie !== "false";
@@ -267,16 +289,16 @@ function processPreprRequest(request, opts) {
267
289
  });
268
290
  }
269
291
  if (!isLivePreview) {
270
- const segmentCookie = requestCookies.get(COOKIE_SEGMENT);
292
+ const segmentCookie = features.segments ? requestCookies.get(COOKIE_SEGMENT) : void 0;
271
293
  if (segmentCookie) {
272
294
  requestHeaders.set("Prepr-Segments", segmentCookie);
273
295
  }
274
- const abCookie = requestCookies.get(COOKIE_VARIANT);
296
+ const abCookie = features.abTesting ? requestCookies.get(COOKIE_VARIANT) : void 0;
275
297
  if (abCookie) {
276
298
  requestHeaders.set("Prepr-ABtesting", abCookie);
277
299
  }
278
300
  }
279
- const previewAb = searchParams.get(PARAM_VARIANT);
301
+ const previewAb = features.abTesting ? searchParams.get(PARAM_VARIANT) : null;
280
302
  if (previewAb !== null) {
281
303
  requestHeaders.set("Prepr-ABtesting", previewAb);
282
304
  if (!isLivePreview) {
@@ -289,7 +311,7 @@ function processPreprRequest(request, opts) {
289
311
  });
290
312
  }
291
313
  }
292
- const previewSegment = searchParams.get(PARAM_SEGMENT);
314
+ const previewSegment = features.segments ? searchParams.get(PARAM_SEGMENT) : null;
293
315
  if (previewSegment !== null) {
294
316
  requestHeaders.set("Prepr-Segments", previewSegment);
295
317
  if (!isLivePreview) {
@@ -329,8 +351,8 @@ function getActiveVariant(headers) {
329
351
  function getPreprHeaders(headers) {
330
352
  return getPreprHeadersFromHeaders(headers);
331
353
  }
332
- async function getToolbarProps(headers, token) {
333
- return getToolbarPropsFromHeaders(headers, token);
354
+ async function getToolbarProps(headers, token, features) {
355
+ return getToolbarPropsFromHeaders(headers, token, features);
334
356
  }
335
357
  // Annotate the CommonJS export names for ESM import in node:
336
358
  0 && (module.exports = {
@@ -1,14 +1,31 @@
1
- interface PreprMiddlewareOptions {
2
- /** Enable preview-mode headers (segment/AB overrides, preview bar). */
3
- preview?: boolean;
4
- /** Override the version reported in Prepr-Package (mainly for tests). */
5
- version?: string;
6
- }
7
-
8
1
  interface PreprSegment {
9
2
  readonly _id: string;
10
3
  readonly name: string;
11
4
  }
5
+ /**
6
+ * Per-feature config. `false` is shorthand for `{ enabled: false }`; the object
7
+ * form leaves room for per-feature options later without a breaking change.
8
+ */
9
+ type PreprFeatureConfig = boolean | {
10
+ enabled?: boolean;
11
+ };
12
+ /** Toolkit features a consumer can turn off. Omitted keys stay enabled. */
13
+ interface PreprFeatures {
14
+ /** Segment picker, `Prepr-Segments` header and its cookie/query param. */
15
+ segments?: PreprFeatureConfig;
16
+ /** A/B variant switch, `Prepr-ABtesting` header and its cookie/query param. */
17
+ abTesting?: PreprFeatureConfig;
18
+ /**
19
+ * The site's own click-to-edit: the toolbar's Edit mode control, the stega
20
+ * tooltip/overlay and the close-edit pill.
21
+ *
22
+ * NOT a security control, and NOT the Prepr editor's live preview. When the
23
+ * site is framed by the editor, `prepr:initVE` still activates edit mode —
24
+ * that is the CMS operating inside its own iframe, not an affordance offered
25
+ * to visitors. See iframe-bridge.ts.
26
+ */
27
+ editMode?: PreprFeatureConfig;
28
+ }
12
29
  interface PreprToolbarProps {
13
30
  readonly activeSegment: string | null;
14
31
  readonly activeVariant: string | null;
@@ -35,6 +52,19 @@ interface PreprHeaders {
35
52
  readonly 'Prepr-User-Agent'?: string;
36
53
  }
37
54
 
55
+ interface PreprMiddlewareOptions {
56
+ /** Enable preview-mode headers (segment/AB overrides, preview bar). */
57
+ preview?: boolean;
58
+ /**
59
+ * Disable features app-wide. Pass the same object you give the toolbar: a
60
+ * disabled feature injects no header and persists no cookie, whatever the
61
+ * request carries.
62
+ */
63
+ features?: PreprFeatures;
64
+ /** Override the version reported in Prepr-Package (mainly for tests). */
65
+ version?: string;
66
+ }
67
+
38
68
  /**
39
69
  * Structural stand-in for Astro's `APIContext`, so this package needs no
40
70
  * build-time dependency on `astro`. Only the fields `onPreprRequest` uses.
@@ -71,6 +101,6 @@ declare function getPreprHeaders(headers: Headers): PreprHeaders;
71
101
  * your deployment already does — the toolkit reads no environment variables of
72
102
  * its own.
73
103
  */
74
- declare function getToolbarProps(headers: Headers, token: string): Promise<PreprToolbarProps>;
104
+ declare function getToolbarProps(headers: Headers, token: string, features?: PreprFeatures): Promise<PreprToolbarProps>;
75
105
 
76
106
  export { type AstroLikeContext, type AstroNext, type PreprMiddlewareOptions, getActiveSegment, getActiveVariant, getPreprHeaders, getPreprUUID, getToolbarProps, onPreprRequest };
@@ -1,14 +1,31 @@
1
- interface PreprMiddlewareOptions {
2
- /** Enable preview-mode headers (segment/AB overrides, preview bar). */
3
- preview?: boolean;
4
- /** Override the version reported in Prepr-Package (mainly for tests). */
5
- version?: string;
6
- }
7
-
8
1
  interface PreprSegment {
9
2
  readonly _id: string;
10
3
  readonly name: string;
11
4
  }
5
+ /**
6
+ * Per-feature config. `false` is shorthand for `{ enabled: false }`; the object
7
+ * form leaves room for per-feature options later without a breaking change.
8
+ */
9
+ type PreprFeatureConfig = boolean | {
10
+ enabled?: boolean;
11
+ };
12
+ /** Toolkit features a consumer can turn off. Omitted keys stay enabled. */
13
+ interface PreprFeatures {
14
+ /** Segment picker, `Prepr-Segments` header and its cookie/query param. */
15
+ segments?: PreprFeatureConfig;
16
+ /** A/B variant switch, `Prepr-ABtesting` header and its cookie/query param. */
17
+ abTesting?: PreprFeatureConfig;
18
+ /**
19
+ * The site's own click-to-edit: the toolbar's Edit mode control, the stega
20
+ * tooltip/overlay and the close-edit pill.
21
+ *
22
+ * NOT a security control, and NOT the Prepr editor's live preview. When the
23
+ * site is framed by the editor, `prepr:initVE` still activates edit mode —
24
+ * that is the CMS operating inside its own iframe, not an affordance offered
25
+ * to visitors. See iframe-bridge.ts.
26
+ */
27
+ editMode?: PreprFeatureConfig;
28
+ }
12
29
  interface PreprToolbarProps {
13
30
  readonly activeSegment: string | null;
14
31
  readonly activeVariant: string | null;
@@ -35,6 +52,19 @@ interface PreprHeaders {
35
52
  readonly 'Prepr-User-Agent'?: string;
36
53
  }
37
54
 
55
+ interface PreprMiddlewareOptions {
56
+ /** Enable preview-mode headers (segment/AB overrides, preview bar). */
57
+ preview?: boolean;
58
+ /**
59
+ * Disable features app-wide. Pass the same object you give the toolbar: a
60
+ * disabled feature injects no header and persists no cookie, whatever the
61
+ * request carries.
62
+ */
63
+ features?: PreprFeatures;
64
+ /** Override the version reported in Prepr-Package (mainly for tests). */
65
+ version?: string;
66
+ }
67
+
38
68
  /**
39
69
  * Structural stand-in for Astro's `APIContext`, so this package needs no
40
70
  * build-time dependency on `astro`. Only the fields `onPreprRequest` uses.
@@ -71,6 +101,6 @@ declare function getPreprHeaders(headers: Headers): PreprHeaders;
71
101
  * your deployment already does — the toolkit reads no environment variables of
72
102
  * its own.
73
103
  */
74
- declare function getToolbarProps(headers: Headers, token: string): Promise<PreprToolbarProps>;
104
+ declare function getToolbarProps(headers: Headers, token: string, features?: PreprFeatures): Promise<PreprToolbarProps>;
75
105
 
76
106
  export { type AstroLikeContext, type AstroNext, type PreprMiddlewareOptions, getActiveSegment, getActiveVariant, getPreprHeaders, getPreprUUID, getToolbarProps, onPreprRequest };
@@ -1,5 +1,23 @@
1
1
  // src/version.ts
2
- var VERSION = "0.1.0-beta.5";
2
+ var VERSION = "0.2.0-beta.2";
3
+
4
+ // src/core/features.ts
5
+ function isEnabled(config) {
6
+ if (typeof config === "boolean") return config;
7
+ return config?.enabled ?? true;
8
+ }
9
+ function resolveFeatures(features) {
10
+ return {
11
+ segments: isEnabled(features?.segments),
12
+ abTesting: isEnabled(features?.abTesting),
13
+ editMode: isEnabled(features?.editMode)
14
+ };
15
+ }
16
+ var ALL_FEATURES_ENABLED = Object.freeze({
17
+ segments: true,
18
+ abTesting: true,
19
+ editMode: true
20
+ });
3
21
 
4
22
  // src/core/server.ts
5
23
  var PreprError = class extends Error {
@@ -107,16 +125,19 @@ async function getPreprEnvironmentSegments(token) {
107
125
  );
108
126
  }
109
127
  }
110
- async function getToolbarPropsFromHeaders(headers, token) {
128
+ async function getToolbarPropsFromHeaders(headers, token, features) {
129
+ const resolved = resolveFeatures(features);
111
130
  let segments = [];
112
- try {
113
- segments = await getPreprEnvironmentSegments(token);
114
- } catch {
115
- segments = [];
131
+ if (resolved.segments) {
132
+ try {
133
+ segments = await getPreprEnvironmentSegments(token);
134
+ } catch {
135
+ segments = [];
136
+ }
116
137
  }
117
138
  return {
118
- activeSegment: getActiveSegmentFromHeaders(headers),
119
- activeVariant: getActiveVariantFromHeaders(headers),
139
+ activeSegment: resolved.segments ? getActiveSegmentFromHeaders(headers) : null,
140
+ activeVariant: resolved.abTesting ? getActiveVariantFromHeaders(headers) : null,
120
141
  segments,
121
142
  data: segments
122
143
  };
@@ -216,7 +237,8 @@ function processPreprRequest(request, opts) {
216
237
  if (!opts?.preview) {
217
238
  return { requestHeaders, responseCookies };
218
239
  }
219
- const hasPreviewParams = searchParams.has(PARAM_SEGMENT) || searchParams.has(PARAM_VARIANT);
240
+ const features = resolveFeatures(opts?.features);
241
+ const hasPreviewParams = features.segments && searchParams.has(PARAM_SEGMENT) || features.abTesting && searchParams.has(PARAM_VARIANT);
220
242
  if (!hasPreviewParams) {
221
243
  const previewModeCookie = requestCookies.get(COOKIE_PREVIEW_MODE);
222
244
  const previewModeEnabled = previewModeCookie !== "false";
@@ -236,16 +258,16 @@ function processPreprRequest(request, opts) {
236
258
  });
237
259
  }
238
260
  if (!isLivePreview) {
239
- const segmentCookie = requestCookies.get(COOKIE_SEGMENT);
261
+ const segmentCookie = features.segments ? requestCookies.get(COOKIE_SEGMENT) : void 0;
240
262
  if (segmentCookie) {
241
263
  requestHeaders.set("Prepr-Segments", segmentCookie);
242
264
  }
243
- const abCookie = requestCookies.get(COOKIE_VARIANT);
265
+ const abCookie = features.abTesting ? requestCookies.get(COOKIE_VARIANT) : void 0;
244
266
  if (abCookie) {
245
267
  requestHeaders.set("Prepr-ABtesting", abCookie);
246
268
  }
247
269
  }
248
- const previewAb = searchParams.get(PARAM_VARIANT);
270
+ const previewAb = features.abTesting ? searchParams.get(PARAM_VARIANT) : null;
249
271
  if (previewAb !== null) {
250
272
  requestHeaders.set("Prepr-ABtesting", previewAb);
251
273
  if (!isLivePreview) {
@@ -258,7 +280,7 @@ function processPreprRequest(request, opts) {
258
280
  });
259
281
  }
260
282
  }
261
- const previewSegment = searchParams.get(PARAM_SEGMENT);
283
+ const previewSegment = features.segments ? searchParams.get(PARAM_SEGMENT) : null;
262
284
  if (previewSegment !== null) {
263
285
  requestHeaders.set("Prepr-Segments", previewSegment);
264
286
  if (!isLivePreview) {
@@ -298,8 +320,8 @@ function getActiveVariant(headers) {
298
320
  function getPreprHeaders(headers) {
299
321
  return getPreprHeadersFromHeaders(headers);
300
322
  }
301
- async function getToolbarProps(headers, token) {
302
- return getToolbarPropsFromHeaders(headers, token);
323
+ async function getToolbarProps(headers, token, features) {
324
+ return getToolbarPropsFromHeaders(headers, token, features);
303
325
  }
304
326
  export {
305
327
  getActiveSegment,