@preprio/toolkit 0.5.0-rc.1 → 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 (69) hide show
  1. package/README.md +300 -19
  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-4JNKYIEV.js → chunk-BJ2LBLWO.js} +69 -268
  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 +74 -111
  21. package/dist/index.d.cts +11 -62
  22. package/dist/index.d.ts +11 -62
  23. package/dist/index.js +23 -167
  24. package/dist/nextjs/chunk-U4HXENF7.js +231 -0
  25. package/dist/nextjs/components.cjs +188 -114
  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 -141
  35. package/dist/nextjs/index.d.ts +53 -141
  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 +86 -112
  53. package/dist/react/components.js +86 -112
  54. package/dist/server-Brr1UgtW.d.ts +59 -0
  55. package/dist/server-DJuS3YU0.d.cts +59 -0
  56. package/dist/sveltekit/chunk-FSARVMG5.js +413 -0
  57. package/dist/sveltekit/graphql.cjs +996 -0
  58. package/dist/sveltekit/graphql.d.cts +233 -0
  59. package/dist/sveltekit/graphql.d.ts +233 -0
  60. package/dist/sveltekit/graphql.js +900 -0
  61. package/dist/sveltekit/index.cjs +14 -1
  62. package/dist/sveltekit/index.d.cts +3 -88
  63. package/dist/sveltekit/index.d.ts +3 -88
  64. package/dist/sveltekit/index.js +15 -387
  65. package/dist/sveltekit/server-CO847mEV.d.cts +98 -0
  66. package/dist/sveltekit/server-CO847mEV.d.ts +98 -0
  67. package/dist/{pixel-e1fUWl2Y.d.cts → types-BYH9XqmN.d.cts} +2 -50
  68. package/dist/{pixel-e1fUWl2Y.d.ts → types-BYH9XqmN.d.ts} +2 -50
  69. 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,7 +294,6 @@ 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
299
  if (allowed) return allowed.includes(origin);
@@ -310,7 +311,6 @@ function isAllowedEditorOrigin(origin, allowed) {
310
311
  }
311
312
  function createIframeBridge(store, options = {}) {
312
313
  const allowedOrigins = options.allowedEditorOrigins;
313
- const onScrollToField = options.onScrollToField;
314
314
  let parentOrigin = null;
315
315
  const onKeyDown = (event) => {
316
316
  const key = event.key.toLowerCase();
@@ -334,15 +334,6 @@ function createIframeBridge(store, options = {}) {
334
334
  const currentScrollY = window.scrollY || document.documentElement.scrollTop;
335
335
  sendPreprEvent("getScrollPosition", { value: currentScrollY });
336
336
  }
337
- if (data?.event === "prepr:scrollToField") {
338
- if (typeof data.field !== "string" || !data.field) return;
339
- if (data.id !== void 0 && typeof data.id !== "string") return;
340
- try {
341
- onScrollToField?.({ field: data.field, id: data.id });
342
- } catch (error) {
343
- debug.warn("scrollToField handler failed", error);
344
- }
345
- }
346
337
  };
347
338
  return {
348
339
  start() {
@@ -367,7 +358,7 @@ var import_stega2 = require("@vercel/stega");
367
358
 
368
359
  // src/core/stega/clean.ts
369
360
  var import_stega = require("@vercel/stega");
370
- var debug2 = createScopedLogger("stega:clean");
361
+ var debug = createScopedLogger("stega:clean");
371
362
  function decodeStega(str) {
372
363
  if (!str) return null;
373
364
  try {
@@ -376,7 +367,7 @@ function decodeStega(str) {
376
367
  return decoded;
377
368
  }
378
369
  } catch (error) {
379
- debug2.log("error decoding stega data", error);
370
+ debug.log("error decoding stega data", error);
380
371
  const match = str.match(/{"origin.*?}/);
381
372
  if (match) {
382
373
  try {
@@ -385,7 +376,7 @@ function decodeStega(str) {
385
376
  return decodedMatch;
386
377
  }
387
378
  } catch (innerError) {
388
- debug2.log("error decoding stega regex match", innerError);
379
+ debug.log("error decoding stega regex match", innerError);
389
380
  }
390
381
  }
391
382
  }
@@ -421,37 +412,15 @@ function isRenderedElement(element) {
421
412
  if (!style) return true;
422
413
  return style.visibility !== "hidden" && style.display !== "none";
423
414
  }
424
- function parseEditHref(href) {
425
- try {
426
- const url = new URL(href, window.location.origin);
427
- return {
428
- id: url.pathname.split("/").pop() || void 0,
429
- field: url.searchParams.get("field") || void 0,
430
- locale: url.searchParams.get("locale") || void 0
431
- };
432
- } catch {
433
- return {};
434
- }
435
- }
436
- function setOrRemove(element, name, value) {
437
- if (value) {
438
- element.setAttribute(name, value);
439
- } else {
440
- element.removeAttribute(name);
441
- }
442
- }
443
415
  function tagEncodedElement(element, decoded) {
444
416
  element.setAttribute("data-prepr-encoded", "");
445
417
  element.setAttribute("data-prepr-href", decoded.href);
446
418
  element.setAttribute("data-prepr-origin", decoded.origin);
447
- const { id, field, locale } = parseEditHref(decoded.href);
448
- setOrRemove(element, "data-prepr-id", id);
449
- setOrRemove(element, "data-prepr-field", field);
450
- setOrRemove(element, "data-prepr-locale", locale);
451
419
  }
452
420
 
453
421
  // src/core/stega/auto-clean.ts
454
- var debug3 = createScopedLogger("stega:auto-clean");
422
+ var debug2 = createScopedLogger("stega:auto-clean");
423
+ var HYDRATION_RETRY_MS = 100;
455
424
  function createStegaAutoClean(options) {
456
425
  const stripText = options?.enabled ?? true;
457
426
  let observer = null;
@@ -460,8 +429,29 @@ function createStegaAutoClean(options) {
460
429
  let pendingHidden = /* @__PURE__ */ new Set();
461
430
  let pendingDirty = /* @__PURE__ */ new Set();
462
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
+ }
463
445
  function cleanTextNode(textNode) {
464
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
+ }
465
455
  const textContent = textNode.textContent;
466
456
  const info = cleaned.get(textNode);
467
457
  const settled = stripText ? info?.cleaned : info?.original;
@@ -494,7 +484,7 @@ function createStegaAutoClean(options) {
494
484
  }
495
485
  pendingHidden.add(textNode);
496
486
  } catch (error) {
497
- debug3.log("error cleaning text node", error);
487
+ debug2.log("error cleaning text node", error);
498
488
  }
499
489
  return false;
500
490
  }
@@ -580,13 +570,17 @@ function createStegaAutoClean(options) {
580
570
  if (observer || typeof window === "undefined") return;
581
571
  scanAndClean();
582
572
  setupObserver();
583
- debug3.log("stega auto-clean started");
573
+ debug2.log("stega auto-clean started");
584
574
  }
585
575
  function stop() {
586
576
  if (debounceTimeout) {
587
577
  clearTimeout(debounceTimeout);
588
578
  debounceTimeout = null;
589
579
  }
580
+ if (hydrationTimeout) {
581
+ clearTimeout(hydrationTimeout);
582
+ hydrationTimeout = null;
583
+ }
590
584
  if (observer) {
591
585
  observer.disconnect();
592
586
  observer = null;
@@ -594,13 +588,14 @@ function createStegaAutoClean(options) {
594
588
  cleaned = /* @__PURE__ */ new WeakMap();
595
589
  pendingHidden = /* @__PURE__ */ new Set();
596
590
  pendingDirty = /* @__PURE__ */ new Set();
597
- debug3.log("stega auto-clean stopped");
591
+ pendingHydration = /* @__PURE__ */ new Set();
592
+ debug2.log("stega auto-clean stopped");
598
593
  }
599
594
  return { start, stop };
600
595
  }
601
596
 
602
597
  // src/core/stega/elements.ts
603
- var debug4 = createScopedLogger("stega:elements");
598
+ var debug3 = createScopedLogger("stega:elements");
604
599
  var StegaElements = class {
605
600
  constructor(decode = decodeStega) {
606
601
  this.observer = null;
@@ -645,7 +640,7 @@ var StegaElements = class {
645
640
  walkTextNodes(document.body, (textNode) => {
646
641
  if (this.tagFromTextNode(textNode)) encodedCount++;
647
642
  });
648
- debug4.log("document scan complete, encoded", encodedCount, "elements");
643
+ debug3.log("document scan complete, encoded", encodedCount, "elements");
649
644
  this.elements = document.querySelectorAll("[data-prepr-encoded]");
650
645
  }
651
646
  /** `onUpdate` fires after each debounced batch, for refreshing derived state. */
@@ -701,9 +696,6 @@ var StegaElements = class {
701
696
  element.removeAttribute("data-prepr-encoded");
702
697
  element.removeAttribute("data-prepr-href");
703
698
  element.removeAttribute("data-prepr-origin");
704
- element.removeAttribute("data-prepr-id");
705
- element.removeAttribute("data-prepr-field");
706
- element.removeAttribute("data-prepr-locale");
707
699
  element.classList.remove("prepr-overlay-active");
708
700
  });
709
701
  this.elements = void 0;
@@ -711,7 +703,7 @@ var StegaElements = class {
711
703
  };
712
704
 
713
705
  // src/core/stega/overlay.ts
714
- var debug5 = createScopedLogger("stega:overlay");
706
+ var debug4 = createScopedLogger("stega:overlay");
715
707
  var StegaOverlay = class {
716
708
  constructor(onEdit) {
717
709
  this.overlay = null;
@@ -756,8 +748,7 @@ var StegaOverlay = class {
756
748
  const origin = element.getAttribute("data-prepr-origin");
757
749
  const id = element.getAttribute("data-prepr-id") ?? void 0;
758
750
  const field = element.getAttribute("data-prepr-field") ?? void 0;
759
- const locale = element.getAttribute("data-prepr-locale") ?? void 0;
760
- debug5.log("showing overlay", { href, origin });
751
+ debug4.log("showing overlay", { href, origin });
761
752
  const overlay = this.overlay;
762
753
  overlay.style.display = "block";
763
754
  overlay.style.top = `${rect.top + window.scrollY - 2}px`;
@@ -789,7 +780,7 @@ var StegaOverlay = class {
789
780
  tooltip.style.left = `${left}px`;
790
781
  });
791
782
  tooltip.onclick = () => {
792
- this.onEdit({ href, origin, id, field, locale });
783
+ this.onEdit({ href, origin, id, field });
793
784
  };
794
785
  }
795
786
  this.currentElement = element;
@@ -829,7 +820,7 @@ var StegaOverlay = class {
829
820
  };
830
821
 
831
822
  // src/core/stega/proximity.ts
832
- var debug6 = createScopedLogger("stega:proximity");
823
+ var debug5 = createScopedLogger("stega:proximity");
833
824
  var StegaProximity = class {
834
825
  constructor() {
835
826
  this.highlighted = /* @__PURE__ */ new Set();
@@ -864,9 +855,9 @@ var StegaProximity = class {
864
855
  );
865
856
  const nodes = this.getEncodedElements();
866
857
  nodes.forEach((el) => this.observer.observe(el));
867
- debug6.log("observing", nodes.length, "encoded elements");
858
+ debug5.log("observing", nodes.length, "encoded elements");
868
859
  } catch (error) {
869
- debug6.log("error setting up IntersectionObserver", error);
860
+ debug5.log("error setting up IntersectionObserver", error);
870
861
  }
871
862
  }
872
863
  stopObserving() {
@@ -936,7 +927,7 @@ function removeStegaStyles() {
936
927
  }
937
928
 
938
929
  // src/core/stega/scan.ts
939
- var debug7 = createScopedLogger("stega:scan");
930
+ var debug6 = createScopedLogger("stega:scan");
940
931
  var EDITOR_MODE_STYLES = "[data-prepr-encoded]{cursor:pointer}";
941
932
  function createStegaController(opts) {
942
933
  const tooltipEnabled = opts.tooltip ?? true;
@@ -969,13 +960,12 @@ function createStegaController(opts) {
969
960
  href: encoded.getAttribute("data-prepr-href") ?? "",
970
961
  origin: encoded.getAttribute("data-prepr-origin") ?? "",
971
962
  id: encoded.getAttribute("data-prepr-id") ?? void 0,
972
- field: encoded.getAttribute("data-prepr-field") ?? void 0,
973
- locale: encoded.getAttribute("data-prepr-locale") ?? void 0
963
+ field: encoded.getAttribute("data-prepr-field") ?? void 0
974
964
  });
975
965
  };
976
966
  const start = () => {
977
967
  if (initialized) {
978
- debug7.log("already initialized, skipping setup");
968
+ debug6.log("already initialized, skipping setup");
979
969
  return;
980
970
  }
981
971
  if (!tooltipEnabled) {
@@ -985,7 +975,7 @@ function createStegaController(opts) {
985
975
  });
986
976
  document.addEventListener("click", handleClick, { capture: true });
987
977
  initialized = true;
988
- debug7.log("stega controller started (editor mode)");
978
+ debug6.log("stega controller started (editor mode)");
989
979
  return;
990
980
  }
991
981
  injectStegaStyles();
@@ -1016,7 +1006,7 @@ function createStegaController(opts) {
1016
1006
  passive: true
1017
1007
  });
1018
1008
  initialized = true;
1019
- debug7.log("stega controller started");
1009
+ debug6.log("stega controller started");
1020
1010
  };
1021
1011
  const stop = () => {
1022
1012
  if (!initialized) return;
@@ -1025,7 +1015,7 @@ function createStegaController(opts) {
1025
1015
  elements.cleanup();
1026
1016
  removeStegaStyles();
1027
1017
  initialized = false;
1028
- debug7.log("stega controller stopped (editor mode)");
1018
+ debug6.log("stega controller stopped (editor mode)");
1029
1019
  return;
1030
1020
  }
1031
1021
  if (throttledMouseMove) {
@@ -1045,43 +1035,11 @@ function createStegaController(opts) {
1045
1035
  elements.cleanup();
1046
1036
  removeStegaStyles();
1047
1037
  initialized = false;
1048
- debug7.log("stega controller stopped");
1038
+ debug6.log("stega controller stopped");
1049
1039
  };
1050
1040
  return { start, stop };
1051
1041
  }
1052
1042
 
1053
- // src/core/stega/scroll-to-field.ts
1054
- var debug8 = createScopedLogger("stega:scroll");
1055
- var FLASH_DURATION_MS = 1200;
1056
- var flashTimeout = null;
1057
- function scrollToField(request) {
1058
- const { field, id } = request;
1059
- if (!field) return false;
1060
- const candidates = document.querySelectorAll(
1061
- "[data-prepr-encoded]"
1062
- );
1063
- let target = null;
1064
- for (const candidate of candidates) {
1065
- if (candidate.getAttribute("data-prepr-field") !== field) continue;
1066
- if (id && candidate.getAttribute("data-prepr-id") !== id) continue;
1067
- target = candidate;
1068
- break;
1069
- }
1070
- if (!target) {
1071
- debug8.log("no element found for field", field);
1072
- return false;
1073
- }
1074
- target.scrollIntoView({ behavior: "smooth", block: "center" });
1075
- if (flashTimeout) clearTimeout(flashTimeout);
1076
- document.querySelectorAll(".prepr-overlay-active").forEach((el) => el.classList.remove("prepr-overlay-active"));
1077
- target.classList.add("prepr-overlay-active");
1078
- flashTimeout = setTimeout(() => {
1079
- target.classList.remove("prepr-overlay-active");
1080
- flashTimeout = null;
1081
- }, FLASH_DURATION_MS);
1082
- return true;
1083
- }
1084
-
1085
1043
  // src/core/store.ts
1086
1044
  var DEFAULT_STATE = {
1087
1045
  locale: "en",
@@ -2194,7 +2152,7 @@ function definePreprToolbar() {
2194
2152
  }
2195
2153
 
2196
2154
  // src/core/create-preview.ts
2197
- var debug9 = createScopedLogger("create-preview");
2155
+ var debug7 = createScopedLogger("create-preview");
2198
2156
  var controllerStores = /* @__PURE__ */ new WeakMap();
2199
2157
  function defaultNavigation() {
2200
2158
  return {
@@ -2236,7 +2194,7 @@ function createPreprPreview(opts = {}) {
2236
2194
  const search = new URLSearchParams(window.location.search);
2237
2195
  const hideBar = search.get(PARAM_HIDE_BAR) === "true";
2238
2196
  if (hideBar) {
2239
- debug9.log(`${PARAM_HIDE_BAR}=true \u2014 no visible toolbar`);
2197
+ debug7.log(`${PARAM_HIDE_BAR}=true \u2014 no visible toolbar`);
2240
2198
  }
2241
2199
  const mountUi = (options?.ui ?? true) && !isIframe && !hideBar;
2242
2200
  const locale = resolveLocale(options);
@@ -2272,17 +2230,18 @@ function createPreprPreview(opts = {}) {
2272
2230
  tooltip: mountUi,
2273
2231
  // In the editor, ask the parent to focus the field instead of opening a new
2274
2232
  // tab. Standalone previews keep the window.open behaviour.
2275
- onEdit: ({ href, id, field, locale: contentLocale }) => {
2233
+ onEdit: ({ href, origin, id, field }) => {
2276
2234
  const safeHref = href ? safeEditUrl(href) : null;
2277
2235
  if (href && !safeHref) {
2278
- debug9.warn("ignored edit request with unsupported href scheme");
2236
+ debug7.warn("ignored edit request with unsupported href scheme");
2279
2237
  return;
2280
2238
  }
2281
2239
  if (isIframe) {
2282
2240
  sendPreprEvent("field_edit_requested", {
2241
+ href: safeHref ?? void 0,
2242
+ origin,
2283
2243
  id,
2284
- field,
2285
- locale: contentLocale
2244
+ field
2286
2245
  });
2287
2246
  } else if (safeHref) {
2288
2247
  window.open(safeHref, "_blank", "noopener,noreferrer");
@@ -2290,7 +2249,8 @@ function createPreprPreview(opts = {}) {
2290
2249
  }
2291
2250
  });
2292
2251
  const autoClean = createStegaAutoClean({
2293
- enabled: options?.autoClean
2252
+ enabled: options?.autoClean,
2253
+ isHydrated: opts.isHydrated
2294
2254
  });
2295
2255
  autoClean.start();
2296
2256
  const handleChange = createChangeHandler({
@@ -2309,15 +2269,7 @@ function createPreprPreview(opts = {}) {
2309
2269
  handleChange(before, state);
2310
2270
  });
2311
2271
  const bridge = createIframeBridge(store, {
2312
- allowedEditorOrigins: options?.allowedEditorOrigins,
2313
- // The editor selecting a field scrolls the matching element into view. The
2314
- // lookup relies on the attributes the stega scan writes, so it finds
2315
- // nothing until encoded content has been tagged.
2316
- onScrollToField: ({ field, id }) => {
2317
- if (!scrollToField({ field, id })) {
2318
- debug9.log("no element on this page renders field", field);
2319
- }
2320
- }
2272
+ allowedEditorOrigins: options?.allowedEditorOrigins
2321
2273
  });
2322
2274
  sendPreprEvent(
2323
2275
  "getScrollPosition",
@@ -2337,18 +2289,101 @@ function createPreprPreview(opts = {}) {
2337
2289
  bridge.stop();
2338
2290
  }
2339
2291
  el?.remove();
2340
- debug9.log("preview destroyed");
2292
+ debug7.log("preview destroyed");
2341
2293
  }
2342
2294
  const controller = { destroy };
2343
2295
  controllerStores.set(controller, store);
2344
2296
  return controller;
2345
2297
  }
2346
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
+
2347
2382
  // src/react/components.ts
2348
2383
  var import_react = require("react");
2349
2384
 
2350
2385
  // src/version.ts
2351
- var VERSION = "0.5.0-rc.1";
2386
+ var VERSION = "0.6.0-alpha.0";
2352
2387
 
2353
2388
  // src/core/pixel.ts
2354
2389
  var PIXEL_SCRIPT_URL = "https://cdn.tracking.prepr.io/js/prepr.min.js";
@@ -2416,14 +2451,53 @@ function PreprToolbar({
2416
2451
  navigation: {
2417
2452
  navigate: (url) => router.push(url),
2418
2453
  currentPath: () => pathnameRef.current + window.location.search
2419
- }
2454
+ },
2455
+ isHydrated: isReactHydrated
2420
2456
  });
2421
2457
  return () => toolbar.destroy();
2422
2458
  }, []);
2423
2459
  return null;
2424
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
+ }
2425
2497
  // Annotate the CommonJS export names for ESM import in node:
2426
2498
  0 && (module.exports = {
2499
+ PreprAbSwitch,
2427
2500
  PreprToolbar,
2428
- PreprTrackingPixel
2501
+ PreprTrackingPixel,
2502
+ PreprVariantTracking
2429
2503
  });
@@ -1,4 +1,6 @@
1
- import { k as PreprPixelConfig, l as PreprToolbarComponentProps } from '../pixel-e1fUWl2Y.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-e1fUWl2Y.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 };