@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/dist/index.cjs CHANGED
@@ -23,7 +23,7 @@ __export(src_exports, {
23
23
  PreprError: () => PreprError,
24
24
  PreprToolbarElement: () => PreprToolbarElement,
25
25
  VERSION: () => VERSION,
26
- createPreprToolbar: () => createPreprToolbar,
26
+ createPreprPreview: () => createPreprPreview,
27
27
  createToolbarStore: () => createToolbarStore,
28
28
  definePreprToolbar: () => definePreprToolbar,
29
29
  extractAccessToken: () => extractAccessToken,
@@ -35,6 +35,7 @@ __export(src_exports, {
35
35
  getToolbarPropsFromHeaders: () => getToolbarPropsFromHeaders,
36
36
  loadTrackingPixel: () => loadTrackingPixel,
37
37
  processPreprRequest: () => processPreprRequest,
38
+ resolveFeatures: () => resolveFeatures,
38
39
  setTrackingParam: () => setTrackingParam,
39
40
  stegaClean: () => stegaClean,
40
41
  trackEvent: () => trackEvent,
@@ -43,7 +44,25 @@ __export(src_exports, {
43
44
  module.exports = __toCommonJS(src_exports);
44
45
 
45
46
  // src/version.ts
46
- var VERSION = "0.1.0-beta.5";
47
+ var VERSION = "0.2.0-beta.2";
48
+
49
+ // src/core/features.ts
50
+ function isEnabled(config) {
51
+ if (typeof config === "boolean") return config;
52
+ return config?.enabled ?? true;
53
+ }
54
+ function resolveFeatures(features) {
55
+ return {
56
+ segments: isEnabled(features?.segments),
57
+ abTesting: isEnabled(features?.abTesting),
58
+ editMode: isEnabled(features?.editMode)
59
+ };
60
+ }
61
+ var ALL_FEATURES_ENABLED = Object.freeze({
62
+ segments: true,
63
+ abTesting: true,
64
+ editMode: true
65
+ });
47
66
 
48
67
  // src/core/constants.ts
49
68
  var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
@@ -326,7 +345,7 @@ function createIframeBridge(store, options = {}) {
326
345
  const top = data.scrollPosition;
327
346
  setTimeout(() => window.scrollTo(0, top), 1);
328
347
  }
329
- store.set({ previewMode: true, editMode: data.editMode ?? true });
348
+ store?.set({ previewMode: true, editMode: data.editMode ?? true });
330
349
  }
331
350
  if (!parentOrigin || evt.origin !== parentOrigin) return;
332
351
  if (data?.event === "prepr:getScrollPosition") {
@@ -952,7 +971,8 @@ var DEFAULT_STATE = {
952
971
  editMode: false,
953
972
  previewMode: false,
954
973
  toolbarOpen: false,
955
- isIframe: false
974
+ isIframe: false,
975
+ features: ALL_FEATURES_ENABLED
956
976
  };
957
977
  function createToolbarStore(initial = {}) {
958
978
  let state = { ...DEFAULT_STATE, ...initial };
@@ -987,7 +1007,7 @@ function cookieOpts() {
987
1007
  };
988
1008
  }
989
1009
  function createChangeHandler(deps) {
990
- const { store, stega, syncAutoClean } = deps;
1010
+ const { store, stega, syncAutoClean, features, editingEnabled } = deps;
991
1011
  function updateParams(patch) {
992
1012
  const [path, existing] = splitPath(deps.currentPath());
993
1013
  const params = new URLSearchParams(existing);
@@ -1003,7 +1023,7 @@ function createChangeHandler(deps) {
1003
1023
  }
1004
1024
  return function handleChange(before, after) {
1005
1025
  const paramPatch = {};
1006
- if (before.selectedSegment !== after.selectedSegment) {
1026
+ if (features.segments && before.selectedSegment !== after.selectedSegment) {
1007
1027
  if (after.selectedSegment === null) {
1008
1028
  removeCookie(COOKIE_SEGMENT, "/", crossSiteCookieOptions());
1009
1029
  } else {
@@ -1014,7 +1034,7 @@ function createChangeHandler(deps) {
1014
1034
  segment: after.selectedSegment ?? void 0
1015
1035
  });
1016
1036
  }
1017
- if (before.selectedVariant !== after.selectedVariant) {
1037
+ if (features.abTesting && before.selectedVariant !== after.selectedVariant) {
1018
1038
  if (after.selectedVariant === null) {
1019
1039
  removeCookie(COOKIE_VARIANT, "/", crossSiteCookieOptions());
1020
1040
  } else {
@@ -1029,7 +1049,7 @@ function createChangeHandler(deps) {
1029
1049
  updateParams(paramPatch);
1030
1050
  }
1031
1051
  if (before.editMode !== after.editMode) {
1032
- if (after.editMode) {
1052
+ if (after.editMode && editingEnabled) {
1033
1053
  stega.start();
1034
1054
  } else {
1035
1055
  stega.stop();
@@ -1600,9 +1620,10 @@ function StatusPill({
1600
1620
  t: t3,
1601
1621
  handlers
1602
1622
  }) {
1623
+ const { features } = state;
1603
1624
  const defaultSegmentName = state.segments.find((s2) => s2._id === "all_other_users")?.name ?? t3("adaptiveContent.allOtherUsers");
1604
1625
  let segmentLabel;
1605
- if (!state.previewMode) {
1626
+ if (!state.previewMode || !features.segments) {
1606
1627
  segmentLabel = t3("common.user");
1607
1628
  } else if (state.selectedSegment !== null) {
1608
1629
  const seg = state.segments.find((s2) => s2._id === state.selectedSegment);
@@ -1626,7 +1647,7 @@ function StatusPill({
1626
1647
  {
1627
1648
  class: "prepr-status-variant",
1628
1649
  "data-prepr": "status-variant",
1629
- hidden: !state.previewMode,
1650
+ hidden: !features.abTesting || !state.previewMode,
1630
1651
  children: state.selectedVariant === "B" ? "B" : "A"
1631
1652
  }
1632
1653
  ),
@@ -1635,7 +1656,7 @@ function StatusPill({
1635
1656
  {
1636
1657
  class: "prepr-status-x",
1637
1658
  "data-prepr": "status-x",
1638
- hidden: !state.previewMode || state.selectedSegment === null,
1659
+ hidden: !features.segments || !state.previewMode || state.selectedSegment === null,
1639
1660
  children: /* @__PURE__ */ u2(RawSvg, { svg: XMARK_ICON_SVG })
1640
1661
  }
1641
1662
  )
@@ -1655,7 +1676,7 @@ function CloseEditPill({
1655
1676
  class: "prepr-close-edit-pill",
1656
1677
  "data-prepr": "close-edit-pill",
1657
1678
  "aria-label": t3("editingTools.ariaCloseEditMode"),
1658
- hidden: !state.editMode || state.isIframe,
1679
+ hidden: !state.features.editMode || !state.editMode || state.isIframe,
1659
1680
  onClick: () => handlers.onCloseEditPill(),
1660
1681
  children: [
1661
1682
  /* @__PURE__ */ u2("span", { "data-prepr": "close-edit-label", children: t3("editingTools.editMode") }),
@@ -1671,7 +1692,9 @@ function Panel({
1671
1692
  segmentFilter,
1672
1693
  handlers
1673
1694
  }) {
1674
- const hasPersonalization = state.selectedSegment !== null || state.selectedVariant !== null;
1695
+ const { features } = state;
1696
+ const hasPersonalization = features.segments && state.selectedSegment !== null || features.abTesting && state.selectedVariant !== null;
1697
+ const showAdaptive = features.segments || features.abTesting;
1675
1698
  return /* @__PURE__ */ u2(S, { children: [
1676
1699
  /* @__PURE__ */ u2(
1677
1700
  "style",
@@ -1714,12 +1737,12 @@ function Panel({
1714
1737
  children: [
1715
1738
  /* @__PURE__ */ u2(Header, { t: t3, onClose: handlers.onClose }),
1716
1739
  /* @__PURE__ */ u2("div", { class: "prepr-section", "data-prepr": "section-adaptive", children: [
1717
- /* @__PURE__ */ u2("span", { class: "prepr-section-label", "data-prepr": "adaptive-label", children: t3("adaptiveContent.adaptiveContent") }),
1740
+ /* @__PURE__ */ u2("span", { class: "prepr-section-label", "data-prepr": "adaptive-label", children: showAdaptive ? t3("adaptiveContent.adaptiveContent") : t3("adaptiveContent.enablePreview") }),
1718
1741
  /* @__PURE__ */ u2("div", { class: "prepr-row", children: [
1719
1742
  /* @__PURE__ */ u2("h2", { class: "prepr-row-title", "data-prepr": "preview-label", children: t3("adaptiveContent.enablePreview") }),
1720
1743
  /* @__PURE__ */ u2(PreviewSelector, { state, t: t3, handlers })
1721
1744
  ] }),
1722
- /* @__PURE__ */ u2("div", { class: "prepr-row", children: [
1745
+ features.segments && /* @__PURE__ */ u2("div", { class: "prepr-row", children: [
1723
1746
  /* @__PURE__ */ u2("h2", { class: "prepr-row-title", "data-prepr": "segment-label", children: t3("adaptiveContent.segment") }),
1724
1747
  /* @__PURE__ */ u2(
1725
1748
  SegmentListbox,
@@ -1732,12 +1755,12 @@ function Panel({
1732
1755
  }
1733
1756
  )
1734
1757
  ] }),
1735
- /* @__PURE__ */ u2("div", { class: "prepr-row", children: [
1758
+ features.abTesting && /* @__PURE__ */ u2("div", { class: "prepr-row", children: [
1736
1759
  /* @__PURE__ */ u2("h2", { class: "prepr-row-title", "data-prepr": "variant-label", children: t3("adaptiveContent.ABVariant") }),
1737
1760
  /* @__PURE__ */ u2(VariantSelector, { state, handlers })
1738
1761
  ] })
1739
1762
  ] }),
1740
- /* @__PURE__ */ u2("div", { class: "prepr-section", "data-prepr": "section-editing", children: [
1763
+ features.editMode && /* @__PURE__ */ u2("div", { class: "prepr-section", "data-prepr": "section-editing", children: [
1741
1764
  /* @__PURE__ */ u2("span", { class: "prepr-section-label", "data-prepr": "editing-label", children: t3("editingTools.editingTools") }),
1742
1765
  /* @__PURE__ */ u2("div", { class: "prepr-row", children: [
1743
1766
  /* @__PURE__ */ u2("h2", { class: "prepr-row-title", "data-prepr": "edit-label", children: t3("editingTools.editMode") }),
@@ -1790,26 +1813,28 @@ var PreprToolbarElement = class extends HTMLElementBase {
1790
1813
  onToggle: () => this.set({ toolbarOpen: !this.state.toolbarOpen }),
1791
1814
  onClose: () => this.set({ toolbarOpen: false }),
1792
1815
  onPreviewMode: (value) => this.set({ previewMode: value }),
1793
- onEditMode: (value) => this.set({ editMode: value }),
1816
+ onEditMode: (value) => {
1817
+ if (!this.state.features.editMode) return;
1818
+ this.set({ editMode: value });
1819
+ },
1794
1820
  onVariant: (value) => {
1821
+ if (!this.state.features.abTesting) return;
1795
1822
  if (!this.state.previewMode) return;
1796
1823
  this.set({ selectedVariant: value });
1797
1824
  },
1798
1825
  // Reset contract: segment to none, variant to 'A' — NOT null, since the
1799
1826
  // resulting `variant_changed` must carry 'A' on the wire — edit mode off.
1800
- onReset: () => this.set({
1801
- selectedSegment: null,
1802
- selectedVariant: "A",
1803
- editMode: false
1804
- }),
1827
+ // Disabled features are left untouched: their state is already inert and
1828
+ // writing it would emit events for a feature the consumer turned off.
1829
+ onReset: () => this.set(this.resetPatch({ editMode: false })),
1805
1830
  onStatusPill: () => {
1806
1831
  if (!this.state.previewMode) {
1807
1832
  this.set({ previewMode: true });
1808
1833
  return;
1809
1834
  }
1810
- if (this.state.selectedSegment !== null || this.state.selectedVariant !== "A") {
1811
- this.set({ selectedSegment: null, selectedVariant: "A" });
1812
- }
1835
+ const patch = this.resetPatch();
1836
+ const dirty = "selectedSegment" in patch && this.state.selectedSegment !== null || "selectedVariant" in patch && this.state.selectedVariant !== "A";
1837
+ if (dirty) this.set(patch);
1813
1838
  },
1814
1839
  onCloseEditPill: () => this.set({ editMode: false }),
1815
1840
  onSegmentButtonClick: () => this.toggleListbox(!this.listboxOpen),
@@ -1859,6 +1884,14 @@ var PreprToolbarElement = class extends HTMLElementBase {
1859
1884
  set(patch) {
1860
1885
  this.store?.set(patch);
1861
1886
  }
1887
+ /** Reset patch covering only the enabled personalization features. */
1888
+ resetPatch(base = {}) {
1889
+ const { features } = this.state;
1890
+ const patch = { ...base };
1891
+ if (features.segments) patch.selectedSegment = null;
1892
+ if (features.abTesting) patch.selectedVariant = "A";
1893
+ return patch;
1894
+ }
1862
1895
  // Synchronous: Preact's top-level render() mutates the DOM before returning.
1863
1896
  renderPanel(state) {
1864
1897
  const root = this.shadowRoot;
@@ -2017,6 +2050,7 @@ var PreprToolbarElement = class extends HTMLElementBase {
2017
2050
  ) ?? null;
2018
2051
  }
2019
2052
  chooseSegment(id) {
2053
+ if (!this.state.features.segments) return;
2020
2054
  this.listboxOpen = false;
2021
2055
  this.segmentFilter = "";
2022
2056
  this.set({ selectedSegment: id });
@@ -2031,8 +2065,8 @@ function definePreprToolbar() {
2031
2065
  }
2032
2066
  }
2033
2067
 
2034
- // src/core/create-toolbar.ts
2035
- var debug7 = createScopedLogger("create-toolbar");
2068
+ // src/core/create-preview.ts
2069
+ var debug7 = createScopedLogger("create-preview");
2036
2070
  var controllerStores = /* @__PURE__ */ new WeakMap();
2037
2071
  function defaultNavigation() {
2038
2072
  return {
@@ -2053,7 +2087,7 @@ function resolveLocale(options) {
2053
2087
  }
2054
2088
  return "en";
2055
2089
  }
2056
- function createPreprToolbar(opts) {
2090
+ function createPreprPreview(opts = {}) {
2057
2091
  const { props, options } = opts;
2058
2092
  const navigation = opts.navigation ?? defaultNavigation();
2059
2093
  initDebugLogger(options?.debug ?? false);
@@ -2066,36 +2100,40 @@ function createPreprToolbar(opts) {
2066
2100
  const search = new URLSearchParams(window.location.search);
2067
2101
  const hideBar = search.get(PARAM_HIDE_BAR) === "true";
2068
2102
  if (hideBar) {
2069
- debug7.log(`${PARAM_HIDE_BAR}=true \u2014 skipping mount`);
2070
- return noop();
2103
+ debug7.log(`${PARAM_HIDE_BAR}=true \u2014 no visible toolbar`);
2071
2104
  }
2105
+ const mountUi = (options?.ui ?? true) && !isIframe && !hideBar;
2072
2106
  const locale = resolveLocale(options);
2107
+ const features = resolveFeatures(options?.features);
2073
2108
  const previewMode = getCookie(COOKIE_PREVIEW_MODE) === "true";
2074
2109
  const toolbarOpen = getCookie(COOKIE_TOOLBAR_OPEN) === "true";
2075
- const cookieSegment = getCookie(COOKIE_SEGMENT);
2076
- const cookieVariant = getCookie(COOKIE_VARIANT);
2077
- const rawVariant = props.activeVariant ?? cookieVariant;
2110
+ const cookieSegment = features.segments ? getCookie(COOKIE_SEGMENT) : null;
2111
+ const cookieVariant = features.abTesting ? getCookie(COOKIE_VARIANT) : null;
2112
+ const rawVariant = features.abTesting ? props?.activeVariant ?? cookieVariant : null;
2078
2113
  const selectedVariant = rawVariant === "A" || rawVariant === "B" ? rawVariant : null;
2079
2114
  const store = createToolbarStore({
2080
2115
  locale,
2081
- segments: buildSegments(props.segments ?? props.data ?? []),
2082
- selectedSegment: props.activeSegment ?? cookieSegment ?? null,
2116
+ features,
2117
+ segments: features.segments ? buildSegments(props?.segments ?? props?.data ?? []) : [],
2118
+ selectedSegment: features.segments ? props?.activeSegment ?? cookieSegment ?? null : null,
2083
2119
  selectedVariant,
2084
2120
  previewMode,
2085
2121
  toolbarOpen,
2086
2122
  isIframe
2087
2123
  });
2088
2124
  let el = null;
2089
- if (!isIframe) {
2125
+ if (mountUi) {
2090
2126
  definePreprToolbar();
2091
2127
  el = document.createElement("prepr-toolbar");
2092
2128
  document.body.appendChild(el);
2093
2129
  el.connect(store, (key) => t(key, locale));
2094
2130
  }
2131
+ const editingEnabled = features.editMode || isIframe;
2095
2132
  const stega = createStegaController({
2096
- // The CMS deep-link tooltip is noise inside the editor; clicking the
2097
- // element itself requests the edit there.
2098
- tooltip: !isIframe,
2133
+ // The CMS deep-link tooltip is chrome, so it follows the same decision as
2134
+ // the bar: noise inside the editor (clicking the element itself requests
2135
+ // the edit there), and unwanted in a headless preview that asked for no UI.
2136
+ tooltip: mountUi,
2099
2137
  // In the editor, ask the parent to focus the field instead of opening a new
2100
2138
  // tab. Standalone previews keep the window.open behaviour.
2101
2139
  onEdit: ({ href, origin, id, field }) => {
@@ -2119,6 +2157,8 @@ function createPreprToolbar(opts) {
2119
2157
  }
2120
2158
  const handleChange = createChangeHandler({
2121
2159
  store,
2160
+ features,
2161
+ editingEnabled,
2122
2162
  navigate: (url) => navigation.navigate(url),
2123
2163
  currentPath: () => navigation.currentPath(),
2124
2164
  reload: navigation.reload ?? (() => window.location.reload()),
@@ -2132,7 +2172,9 @@ function createPreprToolbar(opts) {
2132
2172
  handleChange(before, state);
2133
2173
  });
2134
2174
  syncAutoClean(store.get().previewMode);
2135
- const bridge = createIframeBridge(store);
2175
+ const bridge = createIframeBridge(store, {
2176
+ allowedEditorOrigins: options?.allowedEditorOrigins
2177
+ });
2136
2178
  sendPreprEvent("getScrollPosition", { value: 0 }, {
2137
2179
  allowUntrustedTarget: true
2138
2180
  });
@@ -2147,7 +2189,7 @@ function createPreprToolbar(opts) {
2147
2189
  bridge.stop();
2148
2190
  }
2149
2191
  el?.remove();
2150
- debug7.log("toolbar destroyed");
2192
+ debug7.log("preview destroyed");
2151
2193
  }
2152
2194
  const controller = { destroy };
2153
2195
  controllerStores.set(controller, store);
@@ -2290,16 +2332,19 @@ async function getPreprEnvironmentSegments(token) {
2290
2332
  );
2291
2333
  }
2292
2334
  }
2293
- async function getToolbarPropsFromHeaders(headers, token) {
2335
+ async function getToolbarPropsFromHeaders(headers, token, features) {
2336
+ const resolved = resolveFeatures(features);
2294
2337
  let segments = [];
2295
- try {
2296
- segments = await getPreprEnvironmentSegments(token);
2297
- } catch {
2298
- segments = [];
2338
+ if (resolved.segments) {
2339
+ try {
2340
+ segments = await getPreprEnvironmentSegments(token);
2341
+ } catch {
2342
+ segments = [];
2343
+ }
2299
2344
  }
2300
2345
  return {
2301
- activeSegment: getActiveSegmentFromHeaders(headers),
2302
- activeVariant: getActiveVariantFromHeaders(headers),
2346
+ activeSegment: resolved.segments ? getActiveSegmentFromHeaders(headers) : null,
2347
+ activeVariant: resolved.abTesting ? getActiveVariantFromHeaders(headers) : null,
2303
2348
  segments,
2304
2349
  data: segments
2305
2350
  };
@@ -2374,7 +2419,8 @@ function processPreprRequest(request, opts) {
2374
2419
  if (!opts?.preview) {
2375
2420
  return { requestHeaders, responseCookies };
2376
2421
  }
2377
- const hasPreviewParams = searchParams.has(PARAM_SEGMENT) || searchParams.has(PARAM_VARIANT);
2422
+ const features = resolveFeatures(opts?.features);
2423
+ const hasPreviewParams = features.segments && searchParams.has(PARAM_SEGMENT) || features.abTesting && searchParams.has(PARAM_VARIANT);
2378
2424
  if (!hasPreviewParams) {
2379
2425
  const previewModeCookie = requestCookies.get(COOKIE_PREVIEW_MODE);
2380
2426
  const previewModeEnabled = previewModeCookie !== "false";
@@ -2394,16 +2440,16 @@ function processPreprRequest(request, opts) {
2394
2440
  });
2395
2441
  }
2396
2442
  if (!isLivePreview) {
2397
- const segmentCookie = requestCookies.get(COOKIE_SEGMENT);
2443
+ const segmentCookie = features.segments ? requestCookies.get(COOKIE_SEGMENT) : void 0;
2398
2444
  if (segmentCookie) {
2399
2445
  requestHeaders.set("Prepr-Segments", segmentCookie);
2400
2446
  }
2401
- const abCookie = requestCookies.get(COOKIE_VARIANT);
2447
+ const abCookie = features.abTesting ? requestCookies.get(COOKIE_VARIANT) : void 0;
2402
2448
  if (abCookie) {
2403
2449
  requestHeaders.set("Prepr-ABtesting", abCookie);
2404
2450
  }
2405
2451
  }
2406
- const previewAb = searchParams.get(PARAM_VARIANT);
2452
+ const previewAb = features.abTesting ? searchParams.get(PARAM_VARIANT) : null;
2407
2453
  if (previewAb !== null) {
2408
2454
  requestHeaders.set("Prepr-ABtesting", previewAb);
2409
2455
  if (!isLivePreview) {
@@ -2416,7 +2462,7 @@ function processPreprRequest(request, opts) {
2416
2462
  });
2417
2463
  }
2418
2464
  }
2419
- const previewSegment = searchParams.get(PARAM_SEGMENT);
2465
+ const previewSegment = features.segments ? searchParams.get(PARAM_SEGMENT) : null;
2420
2466
  if (previewSegment !== null) {
2421
2467
  requestHeaders.set("Prepr-Segments", previewSegment);
2422
2468
  if (!isLivePreview) {
@@ -2500,7 +2546,7 @@ function setTrackingParam(key, value) {
2500
2546
  PreprError,
2501
2547
  PreprToolbarElement,
2502
2548
  VERSION,
2503
- createPreprToolbar,
2549
+ createPreprPreview,
2504
2550
  createToolbarStore,
2505
2551
  definePreprToolbar,
2506
2552
  extractAccessToken,
@@ -2512,6 +2558,7 @@ function setTrackingParam(key, value) {
2512
2558
  getToolbarPropsFromHeaders,
2513
2559
  loadTrackingPixel,
2514
2560
  processPreprRequest,
2561
+ resolveFeatures,
2515
2562
  setTrackingParam,
2516
2563
  stegaClean,
2517
2564
  trackEvent,
package/dist/index.d.cts CHANGED
@@ -1,11 +1,18 @@
1
- import { L as Locale, P as PreprSegment, a as PreprVariant, b as PreprToolbarProps, c as PreprToolbarOptions, d as PreprErrorCode, e as PreprHeaders } from './pixel-DVSJQC0K.cjs';
2
- export { f as PreprEnvironment, g as PreprEventType, h as PreprHeaderName, i as PreprPixelConfig, l as loadTrackingPixel, s as setTrackingParam, t as trackEvent } from './pixel-DVSJQC0K.cjs';
1
+ import { P as PreprFeatures, R as ResolvedPreprFeatures, L as Locale, a as PreprSegment, b as PreprVariant, c as PreprToolbarProps, d as PreprPreviewOptions, e as PreprErrorCode, f as PreprHeaders } from './pixel-763p7m6A.cjs';
2
+ export { g as PreprEnvironment, h as PreprEventType, i as PreprFeatureConfig, j as PreprHeaderName, k as PreprPixelConfig, l as PreprToolbarComponentProps, m as PreprToolbarOptions, n as loadTrackingPixel, s as setTrackingParam, t as trackEvent } from './pixel-763p7m6A.cjs';
3
3
 
4
4
  /**
5
5
  * Package version; must match package.json. Lives in its own leaf module so
6
6
  * core can import it without cycling through the barrel.
7
7
  */
8
- declare const VERSION = "0.1.0-beta.5";
8
+ declare const VERSION = "0.2.0-beta.2";
9
+
10
+ /**
11
+ * Resolve a `PreprFeatures` bag to plain booleans, defaulting every omitted key
12
+ * to enabled. Shared by the toolbar and the middleware so both sides agree on
13
+ * what a given config means.
14
+ */
15
+ declare function resolveFeatures(features?: PreprFeatures): ResolvedPreprFeatures;
9
16
 
10
17
  interface ToolbarState {
11
18
  locale: Locale;
@@ -16,6 +23,11 @@ interface ToolbarState {
16
23
  previewMode: boolean;
17
24
  toolbarOpen: boolean;
18
25
  isIframe: boolean;
26
+ /**
27
+ * Set once at construction, never patched. Lives here so the panel and the
28
+ * element read it the same way they read everything else.
29
+ */
30
+ features: ResolvedPreprFeatures;
19
31
  }
20
32
  interface ToolbarStore {
21
33
  get(): ToolbarState;
@@ -25,7 +37,7 @@ interface ToolbarStore {
25
37
  /**
26
38
  * Framework-free store for toolbar state. Deliberately pure: it holds state and
27
39
  * notifies subscribers, nothing else. Cookies, postMessage and reloads are side
28
- * effects owned by the mount controller (create-toolbar.ts).
40
+ * effects owned by the mount controller (create-preview.ts).
29
41
  */
30
42
  declare function createToolbarStore(initial?: Partial<ToolbarState>): ToolbarStore;
31
43
 
@@ -45,21 +57,32 @@ interface PreprNavigationAdapter {
45
57
  */
46
58
  reload?(): void;
47
59
  }
48
- interface CreatePreprToolbarOptions {
49
- props: PreprToolbarProps;
50
- options?: PreprToolbarOptions;
60
+ interface CreatePreprPreviewOptions {
61
+ /**
62
+ * Toolbar data. Optional: a headless preview (`ui: false`) that only wants
63
+ * click-to-edit or scroll restore has no segment list to pass.
64
+ */
65
+ props?: PreprToolbarProps;
66
+ options?: PreprPreviewOptions;
51
67
  navigation?: PreprNavigationAdapter;
52
68
  }
53
- interface PreprToolbarController {
69
+ interface PreprPreviewController {
54
70
  destroy(): void;
55
71
  }
56
72
  /**
57
- * Composition root: hydrates state from props + cookies, mounts
58
- * `<prepr-toolbar>`, and wires the collaborators — the change handler
59
- * (toolbar-change-handler.ts), the stega click-to-edit controller, the
60
- * auto-clean pass and the editor bridge (iframe-bridge.ts).
73
+ * Composition root for the Prepr preview runtime: hydrates state from props +
74
+ * cookies, optionally mounts `<prepr-toolbar>`, and wires the collaborators —
75
+ * the change handler (toolbar-change-handler.ts), the stega click-to-edit
76
+ * controller, the auto-clean pass and the editor bridge (iframe-bridge.ts).
77
+ *
78
+ * Two independent axes control what runs:
79
+ * - `options.features` — which features are active at all (segments, A/B,
80
+ * click-to-edit).
81
+ * - `options.ui` — whether the visible toolbar is mounted. `ui: false` keeps
82
+ * every non-visual side effect wired, which is how a headless preview gets
83
+ * click-to-edit or editor scroll restore with no chrome of its own.
61
84
  */
62
- declare function createPreprToolbar(opts: CreatePreprToolbarOptions): PreprToolbarController;
85
+ declare function createPreprPreview(opts?: CreatePreprPreviewOptions): PreprPreviewController;
63
86
 
64
87
  type Translate = (key: string) => string;
65
88
  declare const HTMLElementBase: typeof HTMLElement;
@@ -69,7 +92,7 @@ declare const HTMLElementBase: typeof HTMLElement;
69
92
  *
70
93
  * This element is the controller: it owns interaction state (listbox open,
71
94
  * keyboard nav, tooltip positioning, outside-click) and writes back ONLY via
72
- * `store.set(...)`. Cookies/postMessage/navigation belong to create-toolbar.ts,
95
+ * `store.set(...)`. Cookies/postMessage/navigation belong to create-preview.ts,
73
96
  * which subscribes to the same store.
74
97
  *
75
98
  * State reaches `<Panel>` as a prop rather than internal Preact state, so every
@@ -87,6 +110,8 @@ declare class PreprToolbarElement extends HTMLElementBase {
87
110
  disconnectedCallback(): void;
88
111
  private get state();
89
112
  private set;
113
+ /** Reset patch covering only the enabled personalization features. */
114
+ private resetPatch;
90
115
  private renderPanel;
91
116
  private tip;
92
117
  /**
@@ -112,6 +137,14 @@ declare class PreprToolbarElement extends HTMLElementBase {
112
137
  /** Idempotent, including across bundles that each try to define the element. */
113
138
  declare function definePreprToolbar(): void;
114
139
 
140
+ interface IframeBridgeOptions {
141
+ /**
142
+ * Replace the `*.prepr.io` wildcard with an exact origin list. Intended for
143
+ * self-hosted editors; when set, the wildcard no longer applies.
144
+ */
145
+ allowedEditorOrigins?: string[];
146
+ }
147
+
115
148
  /** Error thrown by the token helpers and the segments fetch. */
116
149
  declare class PreprError extends Error {
117
150
  readonly code: PreprErrorCode;
@@ -152,8 +185,12 @@ declare function getPreprEnvironmentSegments(token: string): Promise<PreprSegmen
152
185
  * Never throws: a failed segments fetch yields an empty list so the toolbar
153
186
  * still renders. The list is returned under both `segments` and the deprecated
154
187
  * `data` alias.
188
+ *
189
+ * Pass the same `features` object you give the toolbar and the middleware: with
190
+ * segments disabled the `_Segments` request is skipped entirely, saving a
191
+ * round-trip per page.
155
192
  */
156
- declare function getToolbarPropsFromHeaders(headers: Headers, token: string): Promise<PreprToolbarProps>;
193
+ declare function getToolbarPropsFromHeaders(headers: Headers, token: string, features?: PreprFeatures): Promise<PreprToolbarProps>;
157
194
 
158
195
  interface CookieSpec {
159
196
  name: string;
@@ -177,6 +214,12 @@ interface PreprMiddlewareResult {
177
214
  interface PreprMiddlewareOptions {
178
215
  /** Enable preview-mode headers (segment/AB overrides, preview bar). */
179
216
  preview?: boolean;
217
+ /**
218
+ * Disable features app-wide. Pass the same object you give the toolbar: a
219
+ * disabled feature injects no header and persists no cookie, whatever the
220
+ * request carries.
221
+ */
222
+ features?: PreprFeatures;
180
223
  /** Override the version reported in Prepr-Package (mainly for tests). */
181
224
  version?: string;
182
225
  }
@@ -187,4 +230,4 @@ interface PreprMiddlewareOptions {
187
230
  */
188
231
  declare function processPreprRequest(request: Request, opts?: PreprMiddlewareOptions): PreprMiddlewareResult;
189
232
 
190
- export { type CookieSpec, type CreatePreprToolbarOptions, PreprError, PreprErrorCode, PreprHeaders, type PreprMiddlewareOptions, type PreprMiddlewareResult, type PreprNavigationAdapter, PreprSegment, type PreprToolbarController, PreprToolbarElement, PreprToolbarOptions, PreprToolbarProps, PreprVariant, type ToolbarState, type ToolbarStore, VERSION, createPreprToolbar, createToolbarStore, definePreprToolbar, extractAccessToken, getActiveSegmentFromHeaders, getActiveVariantFromHeaders, getPreprEnvironmentSegments, getPreprHeadersFromHeaders, getPreprUUIDFromHeaders, getToolbarPropsFromHeaders, processPreprRequest, stegaClean, validatePreprToken };
233
+ export { type CookieSpec, type CreatePreprPreviewOptions, type IframeBridgeOptions, PreprError, PreprErrorCode, PreprFeatures, PreprHeaders, type PreprMiddlewareOptions, type PreprMiddlewareResult, type PreprNavigationAdapter, type PreprPreviewController, PreprPreviewOptions, PreprSegment, PreprToolbarElement, PreprToolbarProps, PreprVariant, ResolvedPreprFeatures, type ToolbarState, type ToolbarStore, VERSION, createPreprPreview, createToolbarStore, definePreprToolbar, extractAccessToken, getActiveSegmentFromHeaders, getActiveVariantFromHeaders, getPreprEnvironmentSegments, getPreprHeadersFromHeaders, getPreprUUIDFromHeaders, getToolbarPropsFromHeaders, processPreprRequest, resolveFeatures, stegaClean, validatePreprToken };