@preprio/toolkit 0.5.0-rc.2 → 0.6.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +308 -27
  2. package/dist/astro/chunk-3UVUDDNY.js +173 -0
  3. package/dist/astro/graphql.cjs +983 -0
  4. package/dist/astro/graphql.d.cts +231 -0
  5. package/dist/astro/graphql.d.ts +231 -0
  6. package/dist/astro/graphql.js +893 -0
  7. package/dist/astro/index-XT_z-RYT.d.cts +130 -0
  8. package/dist/astro/index-XT_z-RYT.d.ts +130 -0
  9. package/dist/astro/index.cjs +14 -1
  10. package/dist/astro/index.d.cts +1 -120
  11. package/dist/astro/index.d.ts +1 -120
  12. package/dist/astro/index.js +15 -153
  13. package/dist/{chunk-FLPNN3CV.js → chunk-BJ2LBLWO.js} +71 -276
  14. package/dist/chunk-DGZA7KX7.js +172 -0
  15. package/dist/chunk-YR7P4IWE.js +187 -0
  16. package/dist/graphql.cjs +894 -0
  17. package/dist/graphql.d.cts +231 -0
  18. package/dist/graphql.d.ts +231 -0
  19. package/dist/graphql.js +787 -0
  20. package/dist/index.cjs +76 -119
  21. package/dist/index.d.cts +12 -64
  22. package/dist/index.d.ts +12 -64
  23. package/dist/index.js +23 -167
  24. package/dist/nextjs/chunk-U4HXENF7.js +231 -0
  25. package/dist/nextjs/components.cjs +190 -122
  26. package/dist/nextjs/components.d.cts +33 -2
  27. package/dist/nextjs/components.d.ts +33 -2
  28. package/dist/nextjs/components.js +132 -4
  29. package/dist/nextjs/graphql.cjs +1315 -0
  30. package/dist/nextjs/graphql.d.cts +361 -0
  31. package/dist/nextjs/graphql.d.ts +361 -0
  32. package/dist/nextjs/graphql.js +1218 -0
  33. package/dist/nextjs/index.cjs +126 -6
  34. package/dist/nextjs/index.d.cts +53 -147
  35. package/dist/nextjs/index.d.ts +53 -147
  36. package/dist/nextjs/index.js +113 -203
  37. package/dist/nextjs/server-CjpELYvq.d.cts +156 -0
  38. package/dist/nextjs/server-CjpELYvq.d.ts +156 -0
  39. package/dist/nuxt/chunk-P6ENNAEE.js +417 -0
  40. package/dist/nuxt/graphql.cjs +990 -0
  41. package/dist/nuxt/graphql.d.cts +228 -0
  42. package/dist/nuxt/graphql.d.ts +228 -0
  43. package/dist/nuxt/graphql.js +894 -0
  44. package/dist/nuxt/index.cjs +14 -1
  45. package/dist/nuxt/index.d.cts +3 -148
  46. package/dist/nuxt/index.d.ts +3 -148
  47. package/dist/nuxt/index.js +15 -391
  48. package/dist/nuxt/server-8v6OHbsw.d.cts +158 -0
  49. package/dist/nuxt/server-8v6OHbsw.d.ts +158 -0
  50. package/dist/pixel-DwW7cXJm.d.cts +49 -0
  51. package/dist/pixel-DwW7cXJm.d.ts +49 -0
  52. package/dist/react/components.cjs +88 -120
  53. package/dist/react/components.d.cts +2 -8
  54. package/dist/react/components.d.ts +2 -8
  55. package/dist/react/components.js +88 -120
  56. package/dist/server-Brr1UgtW.d.ts +59 -0
  57. package/dist/server-DJuS3YU0.d.cts +59 -0
  58. package/dist/sveltekit/chunk-FSARVMG5.js +413 -0
  59. package/dist/sveltekit/graphql.cjs +996 -0
  60. package/dist/sveltekit/graphql.d.cts +233 -0
  61. package/dist/sveltekit/graphql.d.ts +233 -0
  62. package/dist/sveltekit/graphql.js +900 -0
  63. package/dist/sveltekit/index.cjs +14 -1
  64. package/dist/sveltekit/index.d.cts +3 -88
  65. package/dist/sveltekit/index.d.ts +3 -88
  66. package/dist/sveltekit/index.js +15 -387
  67. package/dist/sveltekit/server-CO847mEV.d.cts +98 -0
  68. package/dist/sveltekit/server-CO847mEV.d.ts +98 -0
  69. package/dist/{pixel-B3oQNbZ3.d.cts → types-BYH9XqmN.d.cts} +4 -58
  70. package/dist/{pixel-B3oQNbZ3.d.ts → types-BYH9XqmN.d.ts} +4 -58
  71. package/package.json +31 -1
@@ -21,8 +21,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  // src/nextjs/components.ts
22
22
  var components_exports = {};
23
23
  __export(components_exports, {
24
+ PreprAbSwitch: () => PreprAbSwitch,
24
25
  PreprToolbar: () => PreprToolbar,
25
- PreprTrackingPixel: () => PreprTrackingPixel
26
+ PreprTrackingPixel: () => PreprTrackingPixel,
27
+ PreprVariantTracking: () => PreprVariantTracking
26
28
  });
27
29
  module.exports = __toCommonJS(components_exports);
28
30
  var import_navigation = require("next/navigation");
@@ -292,13 +294,9 @@ function createElementCache(query, ttl = 1e3) {
292
294
  }
293
295
 
294
296
  // src/core/iframe-bridge.ts
295
- var debug = createScopedLogger("iframe-bridge");
296
297
  var EDITOR_BASE_DOMAIN = "prepr.io";
297
298
  function isAllowedEditorOrigin(origin, allowed) {
298
- if (allowed) {
299
- if (allowed.includes("*")) return origin !== "null";
300
- return allowed.includes(origin);
301
- }
299
+ if (allowed) return allowed.includes(origin);
302
300
  let url;
303
301
  try {
304
302
  url = new URL(origin);
@@ -313,7 +311,6 @@ function isAllowedEditorOrigin(origin, allowed) {
313
311
  }
314
312
  function createIframeBridge(store, options = {}) {
315
313
  const allowedOrigins = options.allowedEditorOrigins;
316
- const onScrollToField = options.onScrollToField;
317
314
  let parentOrigin = null;
318
315
  const onKeyDown = (event) => {
319
316
  const key = event.key.toLowerCase();
@@ -323,10 +320,7 @@ function createIframeBridge(store, options = {}) {
323
320
  const onMessage = (evt) => {
324
321
  const data = evt?.data;
325
322
  if (data?.event === "prepr:initVE" && !parentOrigin) {
326
- if (!isAllowedEditorOrigin(evt.origin, allowedOrigins)) {
327
- debug.warn("ignored editor handshake from origin", evt.origin);
328
- return;
329
- }
323
+ if (!isAllowedEditorOrigin(evt.origin, allowedOrigins)) return;
330
324
  parentOrigin = evt.origin;
331
325
  setTrustedParentOrigin(parentOrigin);
332
326
  if (data.scrollPosition != null) {
@@ -340,15 +334,6 @@ function createIframeBridge(store, options = {}) {
340
334
  const currentScrollY = window.scrollY || document.documentElement.scrollTop;
341
335
  sendPreprEvent("getScrollPosition", { value: currentScrollY });
342
336
  }
343
- if (data?.event === "prepr:scrollToField") {
344
- if (typeof data.field !== "string" || !data.field) return;
345
- if (data.id !== void 0 && typeof data.id !== "string") return;
346
- try {
347
- onScrollToField?.({ field: data.field, id: data.id });
348
- } catch (error) {
349
- debug.warn("scrollToField handler failed", error);
350
- }
351
- }
352
337
  };
353
338
  return {
354
339
  start() {
@@ -373,7 +358,7 @@ var import_stega2 = require("@vercel/stega");
373
358
 
374
359
  // src/core/stega/clean.ts
375
360
  var import_stega = require("@vercel/stega");
376
- var debug2 = createScopedLogger("stega:clean");
361
+ var debug = createScopedLogger("stega:clean");
377
362
  function decodeStega(str) {
378
363
  if (!str) return null;
379
364
  try {
@@ -382,7 +367,7 @@ function decodeStega(str) {
382
367
  return decoded;
383
368
  }
384
369
  } catch (error) {
385
- debug2.log("error decoding stega data", error);
370
+ debug.log("error decoding stega data", error);
386
371
  const match = str.match(/{"origin.*?}/);
387
372
  if (match) {
388
373
  try {
@@ -391,7 +376,7 @@ function decodeStega(str) {
391
376
  return decodedMatch;
392
377
  }
393
378
  } catch (innerError) {
394
- debug2.log("error decoding stega regex match", innerError);
379
+ debug.log("error decoding stega regex match", innerError);
395
380
  }
396
381
  }
397
382
  }
@@ -427,37 +412,15 @@ function isRenderedElement(element) {
427
412
  if (!style) return true;
428
413
  return style.visibility !== "hidden" && style.display !== "none";
429
414
  }
430
- function parseEditHref(href) {
431
- try {
432
- const url = new URL(href, window.location.origin);
433
- return {
434
- id: url.pathname.split("/").pop() || void 0,
435
- field: url.searchParams.get("field") || void 0,
436
- locale: url.searchParams.get("locale") || void 0
437
- };
438
- } catch {
439
- return {};
440
- }
441
- }
442
- function setOrRemove(element, name, value) {
443
- if (value) {
444
- element.setAttribute(name, value);
445
- } else {
446
- element.removeAttribute(name);
447
- }
448
- }
449
415
  function tagEncodedElement(element, decoded) {
450
416
  element.setAttribute("data-prepr-encoded", "");
451
417
  element.setAttribute("data-prepr-href", decoded.href);
452
418
  element.setAttribute("data-prepr-origin", decoded.origin);
453
- const { id, field, locale } = parseEditHref(decoded.href);
454
- setOrRemove(element, "data-prepr-id", id);
455
- setOrRemove(element, "data-prepr-field", field);
456
- setOrRemove(element, "data-prepr-locale", locale);
457
419
  }
458
420
 
459
421
  // src/core/stega/auto-clean.ts
460
- var debug3 = createScopedLogger("stega:auto-clean");
422
+ var debug2 = createScopedLogger("stega:auto-clean");
423
+ var HYDRATION_RETRY_MS = 100;
461
424
  function createStegaAutoClean(options) {
462
425
  const stripText = options?.enabled ?? true;
463
426
  let observer = null;
@@ -466,8 +429,29 @@ function createStegaAutoClean(options) {
466
429
  let pendingHidden = /* @__PURE__ */ new Set();
467
430
  let pendingDirty = /* @__PURE__ */ new Set();
468
431
  let debounceTimeout = null;
432
+ const isHydrated = options?.isHydrated;
433
+ let pendingHydration = /* @__PURE__ */ new Set();
434
+ let hydrationTimeout = null;
435
+ function retryPendingHydration() {
436
+ hydrationTimeout = null;
437
+ processing = true;
438
+ const batch = [...pendingHydration];
439
+ pendingHydration.clear();
440
+ batch.forEach((textNode) => {
441
+ if (textNode.isConnected) cleanTextNode(textNode);
442
+ });
443
+ processing = false;
444
+ }
469
445
  function cleanTextNode(textNode) {
470
446
  if (isIgnoredTextNode(textNode)) return false;
447
+ if (isHydrated && !isHydrated(textNode)) {
448
+ pendingHydration.add(textNode);
449
+ hydrationTimeout ?? (hydrationTimeout = setTimeout(
450
+ retryPendingHydration,
451
+ HYDRATION_RETRY_MS
452
+ ));
453
+ return false;
454
+ }
471
455
  const textContent = textNode.textContent;
472
456
  const info = cleaned.get(textNode);
473
457
  const settled = stripText ? info?.cleaned : info?.original;
@@ -500,7 +484,7 @@ function createStegaAutoClean(options) {
500
484
  }
501
485
  pendingHidden.add(textNode);
502
486
  } catch (error) {
503
- debug3.log("error cleaning text node", error);
487
+ debug2.log("error cleaning text node", error);
504
488
  }
505
489
  return false;
506
490
  }
@@ -586,13 +570,17 @@ function createStegaAutoClean(options) {
586
570
  if (observer || typeof window === "undefined") return;
587
571
  scanAndClean();
588
572
  setupObserver();
589
- debug3.log("stega auto-clean started");
573
+ debug2.log("stega auto-clean started");
590
574
  }
591
575
  function stop() {
592
576
  if (debounceTimeout) {
593
577
  clearTimeout(debounceTimeout);
594
578
  debounceTimeout = null;
595
579
  }
580
+ if (hydrationTimeout) {
581
+ clearTimeout(hydrationTimeout);
582
+ hydrationTimeout = null;
583
+ }
596
584
  if (observer) {
597
585
  observer.disconnect();
598
586
  observer = null;
@@ -600,13 +588,14 @@ function createStegaAutoClean(options) {
600
588
  cleaned = /* @__PURE__ */ new WeakMap();
601
589
  pendingHidden = /* @__PURE__ */ new Set();
602
590
  pendingDirty = /* @__PURE__ */ new Set();
603
- debug3.log("stega auto-clean stopped");
591
+ pendingHydration = /* @__PURE__ */ new Set();
592
+ debug2.log("stega auto-clean stopped");
604
593
  }
605
594
  return { start, stop };
606
595
  }
607
596
 
608
597
  // src/core/stega/elements.ts
609
- var debug4 = createScopedLogger("stega:elements");
598
+ var debug3 = createScopedLogger("stega:elements");
610
599
  var StegaElements = class {
611
600
  constructor(decode = decodeStega) {
612
601
  this.observer = null;
@@ -651,7 +640,7 @@ var StegaElements = class {
651
640
  walkTextNodes(document.body, (textNode) => {
652
641
  if (this.tagFromTextNode(textNode)) encodedCount++;
653
642
  });
654
- debug4.log("document scan complete, encoded", encodedCount, "elements");
643
+ debug3.log("document scan complete, encoded", encodedCount, "elements");
655
644
  this.elements = document.querySelectorAll("[data-prepr-encoded]");
656
645
  }
657
646
  /** `onUpdate` fires after each debounced batch, for refreshing derived state. */
@@ -707,9 +696,6 @@ var StegaElements = class {
707
696
  element.removeAttribute("data-prepr-encoded");
708
697
  element.removeAttribute("data-prepr-href");
709
698
  element.removeAttribute("data-prepr-origin");
710
- element.removeAttribute("data-prepr-id");
711
- element.removeAttribute("data-prepr-field");
712
- element.removeAttribute("data-prepr-locale");
713
699
  element.classList.remove("prepr-overlay-active");
714
700
  });
715
701
  this.elements = void 0;
@@ -717,7 +703,7 @@ var StegaElements = class {
717
703
  };
718
704
 
719
705
  // src/core/stega/overlay.ts
720
- var debug5 = createScopedLogger("stega:overlay");
706
+ var debug4 = createScopedLogger("stega:overlay");
721
707
  var StegaOverlay = class {
722
708
  constructor(onEdit) {
723
709
  this.overlay = null;
@@ -762,8 +748,7 @@ var StegaOverlay = class {
762
748
  const origin = element.getAttribute("data-prepr-origin");
763
749
  const id = element.getAttribute("data-prepr-id") ?? void 0;
764
750
  const field = element.getAttribute("data-prepr-field") ?? void 0;
765
- const locale = element.getAttribute("data-prepr-locale") ?? void 0;
766
- debug5.log("showing overlay", { href, origin });
751
+ debug4.log("showing overlay", { href, origin });
767
752
  const overlay = this.overlay;
768
753
  overlay.style.display = "block";
769
754
  overlay.style.top = `${rect.top + window.scrollY - 2}px`;
@@ -795,7 +780,7 @@ var StegaOverlay = class {
795
780
  tooltip.style.left = `${left}px`;
796
781
  });
797
782
  tooltip.onclick = () => {
798
- this.onEdit({ href, origin, id, field, locale });
783
+ this.onEdit({ href, origin, id, field });
799
784
  };
800
785
  }
801
786
  this.currentElement = element;
@@ -835,7 +820,7 @@ var StegaOverlay = class {
835
820
  };
836
821
 
837
822
  // src/core/stega/proximity.ts
838
- var debug6 = createScopedLogger("stega:proximity");
823
+ var debug5 = createScopedLogger("stega:proximity");
839
824
  var StegaProximity = class {
840
825
  constructor() {
841
826
  this.highlighted = /* @__PURE__ */ new Set();
@@ -870,9 +855,9 @@ var StegaProximity = class {
870
855
  );
871
856
  const nodes = this.getEncodedElements();
872
857
  nodes.forEach((el) => this.observer.observe(el));
873
- debug6.log("observing", nodes.length, "encoded elements");
858
+ debug5.log("observing", nodes.length, "encoded elements");
874
859
  } catch (error) {
875
- debug6.log("error setting up IntersectionObserver", error);
860
+ debug5.log("error setting up IntersectionObserver", error);
876
861
  }
877
862
  }
878
863
  stopObserving() {
@@ -942,7 +927,7 @@ function removeStegaStyles() {
942
927
  }
943
928
 
944
929
  // src/core/stega/scan.ts
945
- var debug7 = createScopedLogger("stega:scan");
930
+ var debug6 = createScopedLogger("stega:scan");
946
931
  var EDITOR_MODE_STYLES = "[data-prepr-encoded]{cursor:pointer}";
947
932
  function createStegaController(opts) {
948
933
  const tooltipEnabled = opts.tooltip ?? true;
@@ -975,13 +960,12 @@ function createStegaController(opts) {
975
960
  href: encoded.getAttribute("data-prepr-href") ?? "",
976
961
  origin: encoded.getAttribute("data-prepr-origin") ?? "",
977
962
  id: encoded.getAttribute("data-prepr-id") ?? void 0,
978
- field: encoded.getAttribute("data-prepr-field") ?? void 0,
979
- locale: encoded.getAttribute("data-prepr-locale") ?? void 0
963
+ field: encoded.getAttribute("data-prepr-field") ?? void 0
980
964
  });
981
965
  };
982
966
  const start = () => {
983
967
  if (initialized) {
984
- debug7.log("already initialized, skipping setup");
968
+ debug6.log("already initialized, skipping setup");
985
969
  return;
986
970
  }
987
971
  if (!tooltipEnabled) {
@@ -991,7 +975,7 @@ function createStegaController(opts) {
991
975
  });
992
976
  document.addEventListener("click", handleClick, { capture: true });
993
977
  initialized = true;
994
- debug7.log("stega controller started (editor mode)");
978
+ debug6.log("stega controller started (editor mode)");
995
979
  return;
996
980
  }
997
981
  injectStegaStyles();
@@ -1022,7 +1006,7 @@ function createStegaController(opts) {
1022
1006
  passive: true
1023
1007
  });
1024
1008
  initialized = true;
1025
- debug7.log("stega controller started");
1009
+ debug6.log("stega controller started");
1026
1010
  };
1027
1011
  const stop = () => {
1028
1012
  if (!initialized) return;
@@ -1031,7 +1015,7 @@ function createStegaController(opts) {
1031
1015
  elements.cleanup();
1032
1016
  removeStegaStyles();
1033
1017
  initialized = false;
1034
- debug7.log("stega controller stopped (editor mode)");
1018
+ debug6.log("stega controller stopped (editor mode)");
1035
1019
  return;
1036
1020
  }
1037
1021
  if (throttledMouseMove) {
@@ -1051,43 +1035,11 @@ function createStegaController(opts) {
1051
1035
  elements.cleanup();
1052
1036
  removeStegaStyles();
1053
1037
  initialized = false;
1054
- debug7.log("stega controller stopped");
1038
+ debug6.log("stega controller stopped");
1055
1039
  };
1056
1040
  return { start, stop };
1057
1041
  }
1058
1042
 
1059
- // src/core/stega/scroll-to-field.ts
1060
- var debug8 = createScopedLogger("stega:scroll");
1061
- var FLASH_DURATION_MS = 1200;
1062
- var flashTimeout = null;
1063
- function scrollToField(request) {
1064
- const { field, id } = request;
1065
- if (!field) return false;
1066
- const candidates = document.querySelectorAll(
1067
- "[data-prepr-encoded]"
1068
- );
1069
- let target = null;
1070
- for (const candidate of candidates) {
1071
- if (candidate.getAttribute("data-prepr-field") !== field) continue;
1072
- if (id && candidate.getAttribute("data-prepr-id") !== id) continue;
1073
- target = candidate;
1074
- break;
1075
- }
1076
- if (!target) {
1077
- debug8.log("no element found for field", field);
1078
- return false;
1079
- }
1080
- target.scrollIntoView({ behavior: "smooth", block: "center" });
1081
- if (flashTimeout) clearTimeout(flashTimeout);
1082
- document.querySelectorAll(".prepr-overlay-active").forEach((el) => el.classList.remove("prepr-overlay-active"));
1083
- target.classList.add("prepr-overlay-active");
1084
- flashTimeout = setTimeout(() => {
1085
- target.classList.remove("prepr-overlay-active");
1086
- flashTimeout = null;
1087
- }, FLASH_DURATION_MS);
1088
- return true;
1089
- }
1090
-
1091
1043
  // src/core/store.ts
1092
1044
  var DEFAULT_STATE = {
1093
1045
  locale: "en",
@@ -2200,7 +2152,7 @@ function definePreprToolbar() {
2200
2152
  }
2201
2153
 
2202
2154
  // src/core/create-preview.ts
2203
- var debug9 = createScopedLogger("create-preview");
2155
+ var debug7 = createScopedLogger("create-preview");
2204
2156
  var controllerStores = /* @__PURE__ */ new WeakMap();
2205
2157
  function defaultNavigation() {
2206
2158
  return {
@@ -2242,7 +2194,7 @@ function createPreprPreview(opts = {}) {
2242
2194
  const search = new URLSearchParams(window.location.search);
2243
2195
  const hideBar = search.get(PARAM_HIDE_BAR) === "true";
2244
2196
  if (hideBar) {
2245
- debug9.log(`${PARAM_HIDE_BAR}=true \u2014 no visible toolbar`);
2197
+ debug7.log(`${PARAM_HIDE_BAR}=true \u2014 no visible toolbar`);
2246
2198
  }
2247
2199
  const mountUi = (options?.ui ?? true) && !isIframe && !hideBar;
2248
2200
  const locale = resolveLocale(options);
@@ -2278,17 +2230,18 @@ function createPreprPreview(opts = {}) {
2278
2230
  tooltip: mountUi,
2279
2231
  // In the editor, ask the parent to focus the field instead of opening a new
2280
2232
  // tab. Standalone previews keep the window.open behaviour.
2281
- onEdit: ({ href, id, field, locale: contentLocale }) => {
2233
+ onEdit: ({ href, origin, id, field }) => {
2282
2234
  const safeHref = href ? safeEditUrl(href) : null;
2283
2235
  if (href && !safeHref) {
2284
- debug9.warn("ignored edit request with unsupported href scheme");
2236
+ debug7.warn("ignored edit request with unsupported href scheme");
2285
2237
  return;
2286
2238
  }
2287
2239
  if (isIframe) {
2288
2240
  sendPreprEvent("field_edit_requested", {
2241
+ href: safeHref ?? void 0,
2242
+ origin,
2289
2243
  id,
2290
- field,
2291
- locale: contentLocale
2244
+ field
2292
2245
  });
2293
2246
  } else if (safeHref) {
2294
2247
  window.open(safeHref, "_blank", "noopener,noreferrer");
@@ -2296,7 +2249,8 @@ function createPreprPreview(opts = {}) {
2296
2249
  }
2297
2250
  });
2298
2251
  const autoClean = createStegaAutoClean({
2299
- enabled: options?.autoClean
2252
+ enabled: options?.autoClean,
2253
+ isHydrated: opts.isHydrated
2300
2254
  });
2301
2255
  autoClean.start();
2302
2256
  const handleChange = createChangeHandler({
@@ -2315,15 +2269,7 @@ function createPreprPreview(opts = {}) {
2315
2269
  handleChange(before, state);
2316
2270
  });
2317
2271
  const bridge = createIframeBridge(store, {
2318
- allowedEditorOrigins: options?.allowedEditorOrigins,
2319
- // The editor selecting a field scrolls the matching element into view. The
2320
- // lookup relies on the attributes the stega scan writes, so it finds
2321
- // nothing until encoded content has been tagged.
2322
- onScrollToField: ({ field, id }) => {
2323
- if (!scrollToField({ field, id })) {
2324
- debug9.log("no element on this page renders field", field);
2325
- }
2326
- }
2272
+ allowedEditorOrigins: options?.allowedEditorOrigins
2327
2273
  });
2328
2274
  sendPreprEvent(
2329
2275
  "getScrollPosition",
@@ -2343,18 +2289,101 @@ function createPreprPreview(opts = {}) {
2343
2289
  bridge.stop();
2344
2290
  }
2345
2291
  el?.remove();
2346
- debug9.log("preview destroyed");
2292
+ debug7.log("preview destroyed");
2347
2293
  }
2348
2294
  const controller = { destroy };
2349
2295
  controllerStores.set(controller, store);
2350
2296
  return controller;
2351
2297
  }
2352
2298
 
2299
+ // src/react/hydration.ts
2300
+ var PROPS_KEY_PREFIX = "__reactProps$";
2301
+ function reactProps(element) {
2302
+ for (const key of Object.keys(element)) {
2303
+ if (key.startsWith(PROPS_KEY_PREFIX)) {
2304
+ return element[key];
2305
+ }
2306
+ }
2307
+ return void 0;
2308
+ }
2309
+ function isReactHydrated(text) {
2310
+ for (let el = text.parentElement; el; el = el.parentElement) {
2311
+ const props = reactProps(el);
2312
+ if (!props) continue;
2313
+ return el === text.parentElement || props.dangerouslySetInnerHTML != null;
2314
+ }
2315
+ return true;
2316
+ }
2317
+
2318
+ // src/nextjs/ab-switch.ts
2319
+ function applyPreprAbBucket() {
2320
+ const match = document.cookie.match(/(?:^|; )prepr_ab_bucket=([AB])(?:;|$)/);
2321
+ const bucket = match ? match[1] : "A";
2322
+ const state = window;
2323
+ if (state.__preprAbBucket === bucket) return;
2324
+ state.__preprAbBucket = bucket;
2325
+ const css = bucket === "A" ? "" : '[data-prepr-ab="A"]{display:none!important}[data-prepr-ab="' + bucket + '"]{display:contents!important}';
2326
+ try {
2327
+ if (!state.__preprAbSheet) {
2328
+ const sheet = new CSSStyleSheet();
2329
+ document.adoptedStyleSheets = document.adoptedStyleSheets.concat(sheet);
2330
+ state.__preprAbSheet = sheet;
2331
+ }
2332
+ state.__preprAbSheet.replaceSync(css);
2333
+ } catch {
2334
+ if (!state.__preprAbSheet) {
2335
+ state.__preprAbSheet = document.createElement("style");
2336
+ document.head.appendChild(state.__preprAbSheet);
2337
+ }
2338
+ state.__preprAbSheet.textContent = css;
2339
+ }
2340
+ }
2341
+ function applyPreprDecisions() {
2342
+ const match = document.cookie.match(/(?:^|; )prepr_decisions=([^;]*)/);
2343
+ let raw;
2344
+ try {
2345
+ raw = match ? decodeURIComponent(match[1]) : "";
2346
+ } catch {
2347
+ raw = "";
2348
+ }
2349
+ const state = window;
2350
+ if (state.__preprDecisions === raw) return;
2351
+ state.__preprDecisions = raw;
2352
+ const id = /^[\w-]+$/;
2353
+ let css = "";
2354
+ raw.split("~").forEach((pair) => {
2355
+ const at = pair.indexOf(":");
2356
+ const group = pair.slice(0, at);
2357
+ if (at < 1 || !id.test(group)) return;
2358
+ const selector = '[data-prepr-group="' + group + '"]';
2359
+ css += selector + "{display:none!important}";
2360
+ pair.slice(at + 1).split("|").forEach((variant) => {
2361
+ if (id.test(variant)) {
2362
+ css += selector + '[data-prepr-variant="' + variant + '"]{display:contents!important}';
2363
+ }
2364
+ });
2365
+ });
2366
+ try {
2367
+ if (!state.__preprDecisionSheet) {
2368
+ const sheet = new CSSStyleSheet();
2369
+ document.adoptedStyleSheets = document.adoptedStyleSheets.concat(sheet);
2370
+ state.__preprDecisionSheet = sheet;
2371
+ }
2372
+ state.__preprDecisionSheet.replaceSync(css);
2373
+ } catch {
2374
+ if (!state.__preprDecisionSheet) {
2375
+ state.__preprDecisionSheet = document.createElement("style");
2376
+ document.head.appendChild(state.__preprDecisionSheet);
2377
+ }
2378
+ state.__preprDecisionSheet.textContent = css;
2379
+ }
2380
+ }
2381
+
2353
2382
  // src/react/components.ts
2354
2383
  var import_react = require("react");
2355
2384
 
2356
2385
  // src/version.ts
2357
- var VERSION = "0.5.0-rc.2";
2386
+ var VERSION = "0.6.0-alpha.0";
2358
2387
 
2359
2388
  // src/core/pixel.ts
2360
2389
  var PIXEL_SCRIPT_URL = "https://cdn.tracking.prepr.io/js/prepr.min.js";
@@ -2422,14 +2451,53 @@ function PreprToolbar({
2422
2451
  navigation: {
2423
2452
  navigate: (url) => router.push(url),
2424
2453
  currentPath: () => pathnameRef.current + window.location.search
2425
- }
2454
+ },
2455
+ isHydrated: isReactHydrated
2426
2456
  });
2427
2457
  return () => toolbar.destroy();
2428
2458
  }, []);
2429
2459
  return null;
2430
2460
  }
2461
+ function PreprAbSwitch() {
2462
+ (0, import_react2.useLayoutEffect)(() => {
2463
+ applyPreprAbBucket();
2464
+ applyPreprDecisions();
2465
+ });
2466
+ return null;
2467
+ }
2468
+ var VARIANT_KEY_ATTRIBUTE = "data-prepr-variant-key";
2469
+ function PreprVariantTracking({
2470
+ variantKeys,
2471
+ ab,
2472
+ children
2473
+ }) {
2474
+ const ref = (0, import_react2.useRef)(null);
2475
+ const keys = variantKeys.join(" ");
2476
+ (0, import_react2.useLayoutEffect)(() => {
2477
+ const elements = [...ref.current?.children ?? []];
2478
+ const list = keys.split(" ");
2479
+ const pairs = elements.length === list.length ? elements.map((element, i2) => [element, list[i2]]) : elements.slice(0, 1).map((element) => [element, list[0]]);
2480
+ const tagged = pairs.filter(([element, key]) => {
2481
+ if (element.hasAttribute(VARIANT_KEY_ATTRIBUTE)) return false;
2482
+ element.setAttribute(VARIANT_KEY_ATTRIBUTE, key);
2483
+ return true;
2484
+ });
2485
+ return () => {
2486
+ for (const [element] of tagged) {
2487
+ element.removeAttribute(VARIANT_KEY_ATTRIBUTE);
2488
+ }
2489
+ };
2490
+ }, [keys]);
2491
+ return (0, import_react2.createElement)(
2492
+ "div",
2493
+ ab === void 0 ? { ref, style: { display: "contents" } } : { ref, "data-prepr-ab": ab },
2494
+ children
2495
+ );
2496
+ }
2431
2497
  // Annotate the CommonJS export names for ESM import in node:
2432
2498
  0 && (module.exports = {
2499
+ PreprAbSwitch,
2433
2500
  PreprToolbar,
2434
- PreprTrackingPixel
2501
+ PreprTrackingPixel,
2502
+ PreprVariantTracking
2435
2503
  });
@@ -1,4 +1,6 @@
1
- import { k as PreprPixelConfig, l as PreprToolbarComponentProps } from '../pixel-B3oQNbZ3.cjs';
1
+ import { ReactNode, ReactElement } from 'react';
2
+ import { k as PreprToolbarComponentProps } from '../types-BYH9XqmN.cjs';
3
+ import { P as PreprPixelConfig } from '../pixel-DwW7cXJm.cjs';
2
4
 
3
5
  interface PreprTrackingPixelProps {
4
6
  /** Prepr tracking/access token. */
@@ -20,5 +22,34 @@ declare function PreprTrackingPixel({ id, config, }: PreprTrackingPixelProps): n
20
22
  * `createPreprPreview` mounts imperatively.
21
23
  */
22
24
  declare function PreprToolbar({ options, ...props }: PreprToolbarComponentProps): null;
25
+ /**
26
+ * Applies the visitor's A/B bucket, and the segment variants decided in the
27
+ * proxy, after client-side navigation. A full page
28
+ * load is covered by the inline script `PreprStack` renders, but React does
29
+ * not run inline scripts that arrive with a client-side navigation; a layout
30
+ * effect still runs before the browser paints the new page. Rendered by
31
+ * `PreprStack`; you do not need it yourself.
32
+ */
33
+ declare function PreprAbSwitch(): null;
34
+ /**
35
+ * Wraps one rendered variant and, once hydrated, puts the variant keys on the
36
+ * elements inside, where Prepr's tracking pixel counts impressions and the
37
+ * clicks of any `data-prepr-variant-event` element in them. The wrapper itself
38
+ * takes no box (`display: contents`), and the pixel only counts an element it
39
+ * can see scroll into view, so the keys belong on the sections. The pixel
40
+ * watches for attributes added after load.
41
+ *
42
+ * A variant can hold several items, each with its own key. When the wrapper
43
+ * holds one element per key they are paired in order; otherwise only the
44
+ * first element is tagged, with the first key. An element that already
45
+ * carries a variant key is left alone. Rendered by `PreprStack` with
46
+ * `trackVariants`; you do not need it yourself.
47
+ */
48
+ declare function PreprVariantTracking({ variantKeys, ab, children, }: {
49
+ variantKeys: string[];
50
+ /** The A/B bucket wrapper this also serves as; its CSS decides `display`. */
51
+ ab?: string;
52
+ children?: ReactNode;
53
+ }): ReactElement;
23
54
 
24
- export { PreprToolbar, PreprTrackingPixel, type PreprTrackingPixelProps };
55
+ export { PreprAbSwitch, PreprToolbar, PreprTrackingPixel, type PreprTrackingPixelProps, PreprVariantTracking };
@@ -1,4 +1,6 @@
1
- import { k as PreprPixelConfig, l as PreprToolbarComponentProps } from '../pixel-B3oQNbZ3.js';
1
+ import { ReactNode, ReactElement } from 'react';
2
+ import { k as PreprToolbarComponentProps } from '../types-BYH9XqmN.js';
3
+ import { P as PreprPixelConfig } from '../pixel-DwW7cXJm.js';
2
4
 
3
5
  interface PreprTrackingPixelProps {
4
6
  /** Prepr tracking/access token. */
@@ -20,5 +22,34 @@ declare function PreprTrackingPixel({ id, config, }: PreprTrackingPixelProps): n
20
22
  * `createPreprPreview` mounts imperatively.
21
23
  */
22
24
  declare function PreprToolbar({ options, ...props }: PreprToolbarComponentProps): null;
25
+ /**
26
+ * Applies the visitor's A/B bucket, and the segment variants decided in the
27
+ * proxy, after client-side navigation. A full page
28
+ * load is covered by the inline script `PreprStack` renders, but React does
29
+ * not run inline scripts that arrive with a client-side navigation; a layout
30
+ * effect still runs before the browser paints the new page. Rendered by
31
+ * `PreprStack`; you do not need it yourself.
32
+ */
33
+ declare function PreprAbSwitch(): null;
34
+ /**
35
+ * Wraps one rendered variant and, once hydrated, puts the variant keys on the
36
+ * elements inside, where Prepr's tracking pixel counts impressions and the
37
+ * clicks of any `data-prepr-variant-event` element in them. The wrapper itself
38
+ * takes no box (`display: contents`), and the pixel only counts an element it
39
+ * can see scroll into view, so the keys belong on the sections. The pixel
40
+ * watches for attributes added after load.
41
+ *
42
+ * A variant can hold several items, each with its own key. When the wrapper
43
+ * holds one element per key they are paired in order; otherwise only the
44
+ * first element is tagged, with the first key. An element that already
45
+ * carries a variant key is left alone. Rendered by `PreprStack` with
46
+ * `trackVariants`; you do not need it yourself.
47
+ */
48
+ declare function PreprVariantTracking({ variantKeys, ab, children, }: {
49
+ variantKeys: string[];
50
+ /** The A/B bucket wrapper this also serves as; its CSS decides `display`. */
51
+ ab?: string;
52
+ children?: ReactNode;
53
+ }): ReactElement;
23
54
 
24
- export { PreprToolbar, PreprTrackingPixel, type PreprTrackingPixelProps };
55
+ export { PreprAbSwitch, PreprToolbar, PreprTrackingPixel, type PreprTrackingPixelProps, PreprVariantTracking };