@preprio/toolkit 0.1.0-beta.6 → 0.2.0-beta.3

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 (40) hide show
  1. package/README.md +178 -13
  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 +103 -50
  12. package/dist/nextjs/components.d.cts +16 -9
  13. package/dist/nextjs/components.d.ts +16 -9
  14. package/dist/nextjs/components.js +23 -11
  15. package/dist/nextjs/index.cjs +37 -15
  16. package/dist/nextjs/index.d.cts +46 -16
  17. package/dist/nextjs/index.d.ts +46 -16
  18. package/dist/nextjs/index.js +37 -15
  19. package/dist/nuxt/index.cjs +37 -15
  20. package/dist/nuxt/index.d.cts +62 -32
  21. package/dist/nuxt/index.d.ts +62 -32
  22. package/dist/nuxt/index.js +37 -15
  23. package/dist/{pixel-DVSJQC0K.d.cts → pixel-763p7m6A.d.cts} +64 -1
  24. package/dist/{pixel-DVSJQC0K.d.ts → pixel-763p7m6A.d.ts} +64 -1
  25. package/dist/react/components.cjs +2237 -0
  26. package/dist/react/components.d.cts +167 -0
  27. package/dist/react/components.d.ts +167 -0
  28. package/dist/react/components.js +2212 -0
  29. package/dist/react/index.cjs +32 -0
  30. package/dist/react/index.d.cts +1 -0
  31. package/dist/react/index.d.ts +1 -0
  32. package/dist/react/index.js +9 -0
  33. package/dist/sveltekit/index.cjs +37 -15
  34. package/dist/sveltekit/index.d.cts +62 -32
  35. package/dist/sveltekit/index.d.ts +62 -32
  36. package/dist/sveltekit/index.js +37 -15
  37. package/package.json +13 -2
  38. package/src/astro/PreprToolbar.astro +16 -10
  39. package/src/nuxt/PreprToolbar.vue +10 -8
  40. package/src/sveltekit/PreprToolbar.svelte +11 -4
@@ -1,3 +1,21 @@
1
+ // src/core/features.ts
2
+ function isEnabled(config) {
3
+ if (typeof config === "boolean") return config;
4
+ return config?.enabled ?? true;
5
+ }
6
+ function resolveFeatures(features) {
7
+ return {
8
+ segments: isEnabled(features?.segments),
9
+ abTesting: isEnabled(features?.abTesting),
10
+ editMode: isEnabled(features?.editMode)
11
+ };
12
+ }
13
+ var ALL_FEATURES_ENABLED = Object.freeze({
14
+ segments: true,
15
+ abTesting: true,
16
+ editMode: true
17
+ });
18
+
1
19
  // src/core/stega/clean.ts
2
20
  import { vercelStegaClean, vercelStegaDecode } from "@vercel/stega";
3
21
 
@@ -706,7 +724,8 @@ var DEFAULT_STATE = {
706
724
  editMode: false,
707
725
  previewMode: false,
708
726
  toolbarOpen: false,
709
- isIframe: false
727
+ isIframe: false,
728
+ features: ALL_FEATURES_ENABLED
710
729
  };
711
730
  function createToolbarStore(initial = {}) {
712
731
  let state = { ...DEFAULT_STATE, ...initial };
@@ -1259,9 +1278,10 @@ function StatusPill({
1259
1278
  t: t3,
1260
1279
  handlers
1261
1280
  }) {
1281
+ const { features } = state;
1262
1282
  const defaultSegmentName = state.segments.find((s2) => s2._id === "all_other_users")?.name ?? t3("adaptiveContent.allOtherUsers");
1263
1283
  let segmentLabel;
1264
- if (!state.previewMode) {
1284
+ if (!state.previewMode || !features.segments) {
1265
1285
  segmentLabel = t3("common.user");
1266
1286
  } else if (state.selectedSegment !== null) {
1267
1287
  const seg = state.segments.find((s2) => s2._id === state.selectedSegment);
@@ -1285,7 +1305,7 @@ function StatusPill({
1285
1305
  {
1286
1306
  class: "prepr-status-variant",
1287
1307
  "data-prepr": "status-variant",
1288
- hidden: !state.previewMode,
1308
+ hidden: !features.abTesting || !state.previewMode,
1289
1309
  children: state.selectedVariant === "B" ? "B" : "A"
1290
1310
  }
1291
1311
  ),
@@ -1294,7 +1314,7 @@ function StatusPill({
1294
1314
  {
1295
1315
  class: "prepr-status-x",
1296
1316
  "data-prepr": "status-x",
1297
- hidden: !state.previewMode || state.selectedSegment === null,
1317
+ hidden: !features.segments || !state.previewMode || state.selectedSegment === null,
1298
1318
  children: /* @__PURE__ */ u2(RawSvg, { svg: XMARK_ICON_SVG })
1299
1319
  }
1300
1320
  )
@@ -1314,7 +1334,7 @@ function CloseEditPill({
1314
1334
  class: "prepr-close-edit-pill",
1315
1335
  "data-prepr": "close-edit-pill",
1316
1336
  "aria-label": t3("editingTools.ariaCloseEditMode"),
1317
- hidden: !state.editMode || state.isIframe,
1337
+ hidden: !state.features.editMode || !state.editMode || state.isIframe,
1318
1338
  onClick: () => handlers.onCloseEditPill(),
1319
1339
  children: [
1320
1340
  /* @__PURE__ */ u2("span", { "data-prepr": "close-edit-label", children: t3("editingTools.editMode") }),
@@ -1330,7 +1350,9 @@ function Panel({
1330
1350
  segmentFilter,
1331
1351
  handlers
1332
1352
  }) {
1333
- const hasPersonalization = state.selectedSegment !== null || state.selectedVariant !== null;
1353
+ const { features } = state;
1354
+ const hasPersonalization = features.segments && state.selectedSegment !== null || features.abTesting && state.selectedVariant !== null;
1355
+ const showAdaptive = features.segments || features.abTesting;
1334
1356
  return /* @__PURE__ */ u2(S, { children: [
1335
1357
  /* @__PURE__ */ u2(
1336
1358
  "style",
@@ -1373,12 +1395,12 @@ function Panel({
1373
1395
  children: [
1374
1396
  /* @__PURE__ */ u2(Header, { t: t3, onClose: handlers.onClose }),
1375
1397
  /* @__PURE__ */ u2("div", { class: "prepr-section", "data-prepr": "section-adaptive", children: [
1376
- /* @__PURE__ */ u2("span", { class: "prepr-section-label", "data-prepr": "adaptive-label", children: t3("adaptiveContent.adaptiveContent") }),
1398
+ /* @__PURE__ */ u2("span", { class: "prepr-section-label", "data-prepr": "adaptive-label", children: showAdaptive ? t3("adaptiveContent.adaptiveContent") : t3("adaptiveContent.enablePreview") }),
1377
1399
  /* @__PURE__ */ u2("div", { class: "prepr-row", children: [
1378
1400
  /* @__PURE__ */ u2("h2", { class: "prepr-row-title", "data-prepr": "preview-label", children: t3("adaptiveContent.enablePreview") }),
1379
1401
  /* @__PURE__ */ u2(PreviewSelector, { state, t: t3, handlers })
1380
1402
  ] }),
1381
- /* @__PURE__ */ u2("div", { class: "prepr-row", children: [
1403
+ features.segments && /* @__PURE__ */ u2("div", { class: "prepr-row", children: [
1382
1404
  /* @__PURE__ */ u2("h2", { class: "prepr-row-title", "data-prepr": "segment-label", children: t3("adaptiveContent.segment") }),
1383
1405
  /* @__PURE__ */ u2(
1384
1406
  SegmentListbox,
@@ -1391,12 +1413,12 @@ function Panel({
1391
1413
  }
1392
1414
  )
1393
1415
  ] }),
1394
- /* @__PURE__ */ u2("div", { class: "prepr-row", children: [
1416
+ features.abTesting && /* @__PURE__ */ u2("div", { class: "prepr-row", children: [
1395
1417
  /* @__PURE__ */ u2("h2", { class: "prepr-row-title", "data-prepr": "variant-label", children: t3("adaptiveContent.ABVariant") }),
1396
1418
  /* @__PURE__ */ u2(VariantSelector, { state, handlers })
1397
1419
  ] })
1398
1420
  ] }),
1399
- /* @__PURE__ */ u2("div", { class: "prepr-section", "data-prepr": "section-editing", children: [
1421
+ features.editMode && /* @__PURE__ */ u2("div", { class: "prepr-section", "data-prepr": "section-editing", children: [
1400
1422
  /* @__PURE__ */ u2("span", { class: "prepr-section-label", "data-prepr": "editing-label", children: t3("editingTools.editingTools") }),
1401
1423
  /* @__PURE__ */ u2("div", { class: "prepr-row", children: [
1402
1424
  /* @__PURE__ */ u2("h2", { class: "prepr-row-title", "data-prepr": "edit-label", children: t3("editingTools.editMode") }),
@@ -1449,26 +1471,28 @@ var PreprToolbarElement = class extends HTMLElementBase {
1449
1471
  onToggle: () => this.set({ toolbarOpen: !this.state.toolbarOpen }),
1450
1472
  onClose: () => this.set({ toolbarOpen: false }),
1451
1473
  onPreviewMode: (value) => this.set({ previewMode: value }),
1452
- onEditMode: (value) => this.set({ editMode: value }),
1474
+ onEditMode: (value) => {
1475
+ if (!this.state.features.editMode) return;
1476
+ this.set({ editMode: value });
1477
+ },
1453
1478
  onVariant: (value) => {
1479
+ if (!this.state.features.abTesting) return;
1454
1480
  if (!this.state.previewMode) return;
1455
1481
  this.set({ selectedVariant: value });
1456
1482
  },
1457
1483
  // Reset contract: segment to none, variant to 'A' — NOT null, since the
1458
1484
  // resulting `variant_changed` must carry 'A' on the wire — edit mode off.
1459
- onReset: () => this.set({
1460
- selectedSegment: null,
1461
- selectedVariant: "A",
1462
- editMode: false
1463
- }),
1485
+ // Disabled features are left untouched: their state is already inert and
1486
+ // writing it would emit events for a feature the consumer turned off.
1487
+ onReset: () => this.set(this.resetPatch({ editMode: false })),
1464
1488
  onStatusPill: () => {
1465
1489
  if (!this.state.previewMode) {
1466
1490
  this.set({ previewMode: true });
1467
1491
  return;
1468
1492
  }
1469
- if (this.state.selectedSegment !== null || this.state.selectedVariant !== "A") {
1470
- this.set({ selectedSegment: null, selectedVariant: "A" });
1471
- }
1493
+ const patch = this.resetPatch();
1494
+ const dirty = "selectedSegment" in patch && this.state.selectedSegment !== null || "selectedVariant" in patch && this.state.selectedVariant !== "A";
1495
+ if (dirty) this.set(patch);
1472
1496
  },
1473
1497
  onCloseEditPill: () => this.set({ editMode: false }),
1474
1498
  onSegmentButtonClick: () => this.toggleListbox(!this.listboxOpen),
@@ -1518,6 +1542,14 @@ var PreprToolbarElement = class extends HTMLElementBase {
1518
1542
  set(patch) {
1519
1543
  this.store?.set(patch);
1520
1544
  }
1545
+ /** Reset patch covering only the enabled personalization features. */
1546
+ resetPatch(base = {}) {
1547
+ const { features } = this.state;
1548
+ const patch = { ...base };
1549
+ if (features.segments) patch.selectedSegment = null;
1550
+ if (features.abTesting) patch.selectedVariant = "A";
1551
+ return patch;
1552
+ }
1521
1553
  // Synchronous: Preact's top-level render() mutates the DOM before returning.
1522
1554
  renderPanel(state) {
1523
1555
  const root = this.shadowRoot;
@@ -1676,6 +1708,7 @@ var PreprToolbarElement = class extends HTMLElementBase {
1676
1708
  ) ?? null;
1677
1709
  }
1678
1710
  chooseSegment(id) {
1711
+ if (!this.state.features.segments) return;
1679
1712
  this.listboxOpen = false;
1680
1713
  this.segmentFilter = "";
1681
1714
  this.set({ selectedSegment: id });
@@ -1866,7 +1899,7 @@ function createIframeBridge(store, options = {}) {
1866
1899
  const top = data.scrollPosition;
1867
1900
  setTimeout(() => window.scrollTo(0, top), 1);
1868
1901
  }
1869
- store.set({ previewMode: true, editMode: data.editMode ?? true });
1902
+ store?.set({ previewMode: true, editMode: data.editMode ?? true });
1870
1903
  }
1871
1904
  if (!parentOrigin || evt.origin !== parentOrigin) return;
1872
1905
  if (data?.event === "prepr:getScrollPosition") {
@@ -1898,7 +1931,7 @@ function cookieOpts() {
1898
1931
  };
1899
1932
  }
1900
1933
  function createChangeHandler(deps) {
1901
- const { store, stega, syncAutoClean } = deps;
1934
+ const { store, stega, syncAutoClean, features, editingEnabled } = deps;
1902
1935
  function updateParams(patch) {
1903
1936
  const [path, existing] = splitPath(deps.currentPath());
1904
1937
  const params = new URLSearchParams(existing);
@@ -1914,7 +1947,7 @@ function createChangeHandler(deps) {
1914
1947
  }
1915
1948
  return function handleChange(before, after) {
1916
1949
  const paramPatch = {};
1917
- if (before.selectedSegment !== after.selectedSegment) {
1950
+ if (features.segments && before.selectedSegment !== after.selectedSegment) {
1918
1951
  if (after.selectedSegment === null) {
1919
1952
  removeCookie(COOKIE_SEGMENT, "/", crossSiteCookieOptions());
1920
1953
  } else {
@@ -1925,7 +1958,7 @@ function createChangeHandler(deps) {
1925
1958
  segment: after.selectedSegment ?? void 0
1926
1959
  });
1927
1960
  }
1928
- if (before.selectedVariant !== after.selectedVariant) {
1961
+ if (features.abTesting && before.selectedVariant !== after.selectedVariant) {
1929
1962
  if (after.selectedVariant === null) {
1930
1963
  removeCookie(COOKIE_VARIANT, "/", crossSiteCookieOptions());
1931
1964
  } else {
@@ -1940,7 +1973,7 @@ function createChangeHandler(deps) {
1940
1973
  updateParams(paramPatch);
1941
1974
  }
1942
1975
  if (before.editMode !== after.editMode) {
1943
- if (after.editMode) {
1976
+ if (after.editMode && editingEnabled) {
1944
1977
  stega.start();
1945
1978
  } else {
1946
1979
  stega.stop();
@@ -1984,8 +2017,8 @@ function splitPath(full) {
1984
2017
  return [full.slice(0, index), full.slice(index + 1)];
1985
2018
  }
1986
2019
 
1987
- // src/core/create-toolbar.ts
1988
- var debug7 = createScopedLogger("create-toolbar");
2020
+ // src/core/create-preview.ts
2021
+ var debug7 = createScopedLogger("create-preview");
1989
2022
  var controllerStores = /* @__PURE__ */ new WeakMap();
1990
2023
  function defaultNavigation() {
1991
2024
  return {
@@ -2006,7 +2039,7 @@ function resolveLocale(options) {
2006
2039
  }
2007
2040
  return "en";
2008
2041
  }
2009
- function createPreprToolbar(opts) {
2042
+ function createPreprPreview(opts = {}) {
2010
2043
  const { props, options } = opts;
2011
2044
  const navigation = opts.navigation ?? defaultNavigation();
2012
2045
  initDebugLogger(options?.debug ?? false);
@@ -2019,36 +2052,40 @@ function createPreprToolbar(opts) {
2019
2052
  const search = new URLSearchParams(window.location.search);
2020
2053
  const hideBar = search.get(PARAM_HIDE_BAR) === "true";
2021
2054
  if (hideBar) {
2022
- debug7.log(`${PARAM_HIDE_BAR}=true \u2014 skipping mount`);
2023
- return noop();
2055
+ debug7.log(`${PARAM_HIDE_BAR}=true \u2014 no visible toolbar`);
2024
2056
  }
2057
+ const mountUi = (options?.ui ?? true) && !isIframe && !hideBar;
2025
2058
  const locale = resolveLocale(options);
2059
+ const features = resolveFeatures(options?.features);
2026
2060
  const previewMode = getCookie(COOKIE_PREVIEW_MODE) === "true";
2027
2061
  const toolbarOpen = getCookie(COOKIE_TOOLBAR_OPEN) === "true";
2028
- const cookieSegment = getCookie(COOKIE_SEGMENT);
2029
- const cookieVariant = getCookie(COOKIE_VARIANT);
2030
- const rawVariant = props.activeVariant ?? cookieVariant;
2062
+ const cookieSegment = features.segments ? getCookie(COOKIE_SEGMENT) : null;
2063
+ const cookieVariant = features.abTesting ? getCookie(COOKIE_VARIANT) : null;
2064
+ const rawVariant = features.abTesting ? props?.activeVariant ?? cookieVariant : null;
2031
2065
  const selectedVariant = rawVariant === "A" || rawVariant === "B" ? rawVariant : null;
2032
2066
  const store = createToolbarStore({
2033
2067
  locale,
2034
- segments: buildSegments(props.segments ?? props.data ?? []),
2035
- selectedSegment: props.activeSegment ?? cookieSegment ?? null,
2068
+ features,
2069
+ segments: features.segments ? buildSegments(props?.segments ?? props?.data ?? []) : [],
2070
+ selectedSegment: features.segments ? props?.activeSegment ?? cookieSegment ?? null : null,
2036
2071
  selectedVariant,
2037
2072
  previewMode,
2038
2073
  toolbarOpen,
2039
2074
  isIframe
2040
2075
  });
2041
2076
  let el = null;
2042
- if (!isIframe) {
2077
+ if (mountUi) {
2043
2078
  definePreprToolbar();
2044
2079
  el = document.createElement("prepr-toolbar");
2045
2080
  document.body.appendChild(el);
2046
2081
  el.connect(store, (key) => t2(key, locale));
2047
2082
  }
2083
+ const editingEnabled = features.editMode || isIframe;
2048
2084
  const stega = createStegaController({
2049
- // The CMS deep-link tooltip is noise inside the editor; clicking the
2050
- // element itself requests the edit there.
2051
- tooltip: !isIframe,
2085
+ // The CMS deep-link tooltip is chrome, so it follows the same decision as
2086
+ // the bar: noise inside the editor (clicking the element itself requests
2087
+ // the edit there), and unwanted in a headless preview that asked for no UI.
2088
+ tooltip: mountUi,
2052
2089
  // In the editor, ask the parent to focus the field instead of opening a new
2053
2090
  // tab. Standalone previews keep the window.open behaviour.
2054
2091
  onEdit: ({ href, origin, id, field }) => {
@@ -2072,6 +2109,8 @@ function createPreprToolbar(opts) {
2072
2109
  }
2073
2110
  const handleChange = createChangeHandler({
2074
2111
  store,
2112
+ features,
2113
+ editingEnabled,
2075
2114
  navigate: (url) => navigation.navigate(url),
2076
2115
  currentPath: () => navigation.currentPath(),
2077
2116
  reload: navigation.reload ?? (() => window.location.reload()),
@@ -2085,7 +2124,9 @@ function createPreprToolbar(opts) {
2085
2124
  handleChange(before, state);
2086
2125
  });
2087
2126
  syncAutoClean(store.get().previewMode);
2088
- const bridge = createIframeBridge(store);
2127
+ const bridge = createIframeBridge(store, {
2128
+ allowedEditorOrigins: options?.allowedEditorOrigins
2129
+ });
2089
2130
  sendPreprEvent("getScrollPosition", { value: 0 }, {
2090
2131
  allowUntrustedTarget: true
2091
2132
  });
@@ -2100,7 +2141,7 @@ function createPreprToolbar(opts) {
2100
2141
  bridge.stop();
2101
2142
  }
2102
2143
  el?.remove();
2103
- debug7.log("toolbar destroyed");
2144
+ debug7.log("preview destroyed");
2104
2145
  }
2105
2146
  const controller = { destroy };
2106
2147
  controllerStores.set(controller, store);
@@ -2172,6 +2213,7 @@ function setTrackingParam(key, value) {
2172
2213
  }
2173
2214
 
2174
2215
  export {
2216
+ resolveFeatures,
2175
2217
  COOKIE_PREVIEW_MODE,
2176
2218
  COOKIE_SEGMENT,
2177
2219
  COOKIE_VARIANT,
@@ -2185,7 +2227,7 @@ export {
2185
2227
  createToolbarStore,
2186
2228
  PreprToolbarElement,
2187
2229
  definePreprToolbar,
2188
- createPreprToolbar,
2230
+ createPreprPreview,
2189
2231
  loadTrackingPixel,
2190
2232
  trackEvent,
2191
2233
  setTrackingParam