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

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 (78) hide show
  1. package/README.md +415 -27
  2. package/dist/astro/chunk-O7N6GPAH.js +173 -0
  3. package/dist/astro/graphql.cjs +1030 -0
  4. package/dist/astro/graphql.d.cts +305 -0
  5. package/dist/astro/graphql.d.ts +305 -0
  6. package/dist/astro/graphql.js +936 -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-3JLWZQ37.js +179 -0
  14. package/dist/chunk-3NCEVJJ3.js +10 -0
  15. package/dist/chunk-S5RWVMDJ.js +148 -0
  16. package/dist/chunk-TBKSBXGH.js +452 -0
  17. package/dist/{chunk-FLPNN3CV.js → chunk-VI6QZTBM.js} +73 -276
  18. package/dist/chunk-YWIKKWBR.js +26 -0
  19. package/dist/graphql/codegen.cjs +510 -0
  20. package/dist/graphql/codegen.d.cts +23 -0
  21. package/dist/graphql/codegen.d.ts +23 -0
  22. package/dist/graphql/codegen.js +99 -0
  23. package/dist/graphql.cjs +941 -0
  24. package/dist/graphql.d.cts +305 -0
  25. package/dist/graphql.d.ts +305 -0
  26. package/dist/graphql.js +419 -0
  27. package/dist/index.cjs +76 -119
  28. package/dist/index.d.cts +12 -64
  29. package/dist/index.d.ts +12 -64
  30. package/dist/index.js +27 -167
  31. package/dist/nextjs/chunk-BQ6IGSH6.js +231 -0
  32. package/dist/nextjs/components.cjs +190 -122
  33. package/dist/nextjs/components.d.cts +33 -2
  34. package/dist/nextjs/components.d.ts +33 -2
  35. package/dist/nextjs/components.js +133 -4
  36. package/dist/nextjs/graphql.cjs +1362 -0
  37. package/dist/nextjs/graphql.d.cts +435 -0
  38. package/dist/nextjs/graphql.d.ts +435 -0
  39. package/dist/nextjs/graphql.js +1261 -0
  40. package/dist/nextjs/index.cjs +126 -6
  41. package/dist/nextjs/index.d.cts +53 -147
  42. package/dist/nextjs/index.d.ts +53 -147
  43. package/dist/nextjs/index.js +113 -203
  44. package/dist/nextjs/server-CjpELYvq.d.cts +156 -0
  45. package/dist/nextjs/server-CjpELYvq.d.ts +156 -0
  46. package/dist/nuxt/chunk-IQDSBPDN.js +417 -0
  47. package/dist/nuxt/graphql.cjs +1037 -0
  48. package/dist/nuxt/graphql.d.cts +302 -0
  49. package/dist/nuxt/graphql.d.ts +302 -0
  50. package/dist/nuxt/graphql.js +937 -0
  51. package/dist/nuxt/index.cjs +14 -1
  52. package/dist/nuxt/index.d.cts +3 -148
  53. package/dist/nuxt/index.d.ts +3 -148
  54. package/dist/nuxt/index.js +15 -391
  55. package/dist/nuxt/server-8v6OHbsw.d.cts +158 -0
  56. package/dist/nuxt/server-8v6OHbsw.d.ts +158 -0
  57. package/dist/pixel-DwW7cXJm.d.cts +49 -0
  58. package/dist/pixel-DwW7cXJm.d.ts +49 -0
  59. package/dist/react/components.cjs +88 -120
  60. package/dist/react/components.d.cts +2 -8
  61. package/dist/react/components.d.ts +2 -8
  62. package/dist/react/components.js +88 -120
  63. package/dist/server-Brr1UgtW.d.ts +59 -0
  64. package/dist/server-DJuS3YU0.d.cts +59 -0
  65. package/dist/sveltekit/chunk-OKYZ6HPH.js +413 -0
  66. package/dist/sveltekit/graphql.cjs +1043 -0
  67. package/dist/sveltekit/graphql.d.cts +307 -0
  68. package/dist/sveltekit/graphql.d.ts +307 -0
  69. package/dist/sveltekit/graphql.js +943 -0
  70. package/dist/sveltekit/index.cjs +14 -1
  71. package/dist/sveltekit/index.d.cts +3 -88
  72. package/dist/sveltekit/index.d.ts +3 -88
  73. package/dist/sveltekit/index.js +15 -387
  74. package/dist/sveltekit/server-CO847mEV.d.cts +98 -0
  75. package/dist/sveltekit/server-CO847mEV.d.ts +98 -0
  76. package/dist/{pixel-B3oQNbZ3.d.cts → types-BYH9XqmN.d.cts} +4 -58
  77. package/dist/{pixel-B3oQNbZ3.d.ts → types-BYH9XqmN.d.ts} +4 -58
  78. package/package.json +36 -1
@@ -267,13 +267,9 @@ function createElementCache(query, ttl = 1e3) {
267
267
  }
268
268
 
269
269
  // src/core/iframe-bridge.ts
270
- var debug = createScopedLogger("iframe-bridge");
271
270
  var EDITOR_BASE_DOMAIN = "prepr.io";
272
271
  function isAllowedEditorOrigin(origin, allowed) {
273
- if (allowed) {
274
- if (allowed.includes("*")) return origin !== "null";
275
- return allowed.includes(origin);
276
- }
272
+ if (allowed) return allowed.includes(origin);
277
273
  let url;
278
274
  try {
279
275
  url = new URL(origin);
@@ -288,7 +284,6 @@ function isAllowedEditorOrigin(origin, allowed) {
288
284
  }
289
285
  function createIframeBridge(store, options = {}) {
290
286
  const allowedOrigins = options.allowedEditorOrigins;
291
- const onScrollToField = options.onScrollToField;
292
287
  let parentOrigin = null;
293
288
  const onKeyDown = (event) => {
294
289
  const key = event.key.toLowerCase();
@@ -298,10 +293,7 @@ function createIframeBridge(store, options = {}) {
298
293
  const onMessage = (evt) => {
299
294
  const data = evt?.data;
300
295
  if (data?.event === "prepr:initVE" && !parentOrigin) {
301
- if (!isAllowedEditorOrigin(evt.origin, allowedOrigins)) {
302
- debug.warn("ignored editor handshake from origin", evt.origin);
303
- return;
304
- }
296
+ if (!isAllowedEditorOrigin(evt.origin, allowedOrigins)) return;
305
297
  parentOrigin = evt.origin;
306
298
  setTrustedParentOrigin(parentOrigin);
307
299
  if (data.scrollPosition != null) {
@@ -315,15 +307,6 @@ function createIframeBridge(store, options = {}) {
315
307
  const currentScrollY = window.scrollY || document.documentElement.scrollTop;
316
308
  sendPreprEvent("getScrollPosition", { value: currentScrollY });
317
309
  }
318
- if (data?.event === "prepr:scrollToField") {
319
- if (typeof data.field !== "string" || !data.field) return;
320
- if (data.id !== void 0 && typeof data.id !== "string") return;
321
- try {
322
- onScrollToField?.({ field: data.field, id: data.id });
323
- } catch (error) {
324
- debug.warn("scrollToField handler failed", error);
325
- }
326
- }
327
310
  };
328
311
  return {
329
312
  start() {
@@ -348,7 +331,7 @@ import { vercelStegaSplit } from "@vercel/stega";
348
331
 
349
332
  // src/core/stega/clean.ts
350
333
  import { vercelStegaClean, vercelStegaDecode } from "@vercel/stega";
351
- var debug2 = createScopedLogger("stega:clean");
334
+ var debug = createScopedLogger("stega:clean");
352
335
  function decodeStega(str) {
353
336
  if (!str) return null;
354
337
  try {
@@ -357,7 +340,7 @@ function decodeStega(str) {
357
340
  return decoded;
358
341
  }
359
342
  } catch (error) {
360
- debug2.log("error decoding stega data", error);
343
+ debug.log("error decoding stega data", error);
361
344
  const match = str.match(/{"origin.*?}/);
362
345
  if (match) {
363
346
  try {
@@ -366,7 +349,7 @@ function decodeStega(str) {
366
349
  return decodedMatch;
367
350
  }
368
351
  } catch (innerError) {
369
- debug2.log("error decoding stega regex match", innerError);
352
+ debug.log("error decoding stega regex match", innerError);
370
353
  }
371
354
  }
372
355
  }
@@ -402,37 +385,15 @@ function isRenderedElement(element) {
402
385
  if (!style) return true;
403
386
  return style.visibility !== "hidden" && style.display !== "none";
404
387
  }
405
- function parseEditHref(href) {
406
- try {
407
- const url = new URL(href, window.location.origin);
408
- return {
409
- id: url.pathname.split("/").pop() || void 0,
410
- field: url.searchParams.get("field") || void 0,
411
- locale: url.searchParams.get("locale") || void 0
412
- };
413
- } catch {
414
- return {};
415
- }
416
- }
417
- function setOrRemove(element, name, value) {
418
- if (value) {
419
- element.setAttribute(name, value);
420
- } else {
421
- element.removeAttribute(name);
422
- }
423
- }
424
388
  function tagEncodedElement(element, decoded) {
425
389
  element.setAttribute("data-prepr-encoded", "");
426
390
  element.setAttribute("data-prepr-href", decoded.href);
427
391
  element.setAttribute("data-prepr-origin", decoded.origin);
428
- const { id, field, locale } = parseEditHref(decoded.href);
429
- setOrRemove(element, "data-prepr-id", id);
430
- setOrRemove(element, "data-prepr-field", field);
431
- setOrRemove(element, "data-prepr-locale", locale);
432
392
  }
433
393
 
434
394
  // src/core/stega/auto-clean.ts
435
- var debug3 = createScopedLogger("stega:auto-clean");
395
+ var debug2 = createScopedLogger("stega:auto-clean");
396
+ var HYDRATION_RETRY_MS = 100;
436
397
  function createStegaAutoClean(options) {
437
398
  const stripText = options?.enabled ?? true;
438
399
  let observer = null;
@@ -441,8 +402,29 @@ function createStegaAutoClean(options) {
441
402
  let pendingHidden = /* @__PURE__ */ new Set();
442
403
  let pendingDirty = /* @__PURE__ */ new Set();
443
404
  let debounceTimeout = null;
405
+ const isHydrated = options?.isHydrated;
406
+ let pendingHydration = /* @__PURE__ */ new Set();
407
+ let hydrationTimeout = null;
408
+ function retryPendingHydration() {
409
+ hydrationTimeout = null;
410
+ processing = true;
411
+ const batch = [...pendingHydration];
412
+ pendingHydration.clear();
413
+ batch.forEach((textNode) => {
414
+ if (textNode.isConnected) cleanTextNode(textNode);
415
+ });
416
+ processing = false;
417
+ }
444
418
  function cleanTextNode(textNode) {
445
419
  if (isIgnoredTextNode(textNode)) return false;
420
+ if (isHydrated && !isHydrated(textNode)) {
421
+ pendingHydration.add(textNode);
422
+ hydrationTimeout ?? (hydrationTimeout = setTimeout(
423
+ retryPendingHydration,
424
+ HYDRATION_RETRY_MS
425
+ ));
426
+ return false;
427
+ }
446
428
  const textContent = textNode.textContent;
447
429
  const info = cleaned.get(textNode);
448
430
  const settled = stripText ? info?.cleaned : info?.original;
@@ -475,7 +457,7 @@ function createStegaAutoClean(options) {
475
457
  }
476
458
  pendingHidden.add(textNode);
477
459
  } catch (error) {
478
- debug3.log("error cleaning text node", error);
460
+ debug2.log("error cleaning text node", error);
479
461
  }
480
462
  return false;
481
463
  }
@@ -561,13 +543,17 @@ function createStegaAutoClean(options) {
561
543
  if (observer || typeof window === "undefined") return;
562
544
  scanAndClean();
563
545
  setupObserver();
564
- debug3.log("stega auto-clean started");
546
+ debug2.log("stega auto-clean started");
565
547
  }
566
548
  function stop() {
567
549
  if (debounceTimeout) {
568
550
  clearTimeout(debounceTimeout);
569
551
  debounceTimeout = null;
570
552
  }
553
+ if (hydrationTimeout) {
554
+ clearTimeout(hydrationTimeout);
555
+ hydrationTimeout = null;
556
+ }
571
557
  if (observer) {
572
558
  observer.disconnect();
573
559
  observer = null;
@@ -575,13 +561,14 @@ function createStegaAutoClean(options) {
575
561
  cleaned = /* @__PURE__ */ new WeakMap();
576
562
  pendingHidden = /* @__PURE__ */ new Set();
577
563
  pendingDirty = /* @__PURE__ */ new Set();
578
- debug3.log("stega auto-clean stopped");
564
+ pendingHydration = /* @__PURE__ */ new Set();
565
+ debug2.log("stega auto-clean stopped");
579
566
  }
580
567
  return { start, stop };
581
568
  }
582
569
 
583
570
  // src/core/stega/elements.ts
584
- var debug4 = createScopedLogger("stega:elements");
571
+ var debug3 = createScopedLogger("stega:elements");
585
572
  var StegaElements = class {
586
573
  constructor(decode = decodeStega) {
587
574
  this.observer = null;
@@ -626,7 +613,7 @@ var StegaElements = class {
626
613
  walkTextNodes(document.body, (textNode) => {
627
614
  if (this.tagFromTextNode(textNode)) encodedCount++;
628
615
  });
629
- debug4.log("document scan complete, encoded", encodedCount, "elements");
616
+ debug3.log("document scan complete, encoded", encodedCount, "elements");
630
617
  this.elements = document.querySelectorAll("[data-prepr-encoded]");
631
618
  }
632
619
  /** `onUpdate` fires after each debounced batch, for refreshing derived state. */
@@ -682,9 +669,6 @@ var StegaElements = class {
682
669
  element.removeAttribute("data-prepr-encoded");
683
670
  element.removeAttribute("data-prepr-href");
684
671
  element.removeAttribute("data-prepr-origin");
685
- element.removeAttribute("data-prepr-id");
686
- element.removeAttribute("data-prepr-field");
687
- element.removeAttribute("data-prepr-locale");
688
672
  element.classList.remove("prepr-overlay-active");
689
673
  });
690
674
  this.elements = void 0;
@@ -692,7 +676,7 @@ var StegaElements = class {
692
676
  };
693
677
 
694
678
  // src/core/stega/overlay.ts
695
- var debug5 = createScopedLogger("stega:overlay");
679
+ var debug4 = createScopedLogger("stega:overlay");
696
680
  var StegaOverlay = class {
697
681
  constructor(onEdit) {
698
682
  this.overlay = null;
@@ -737,8 +721,7 @@ var StegaOverlay = class {
737
721
  const origin = element.getAttribute("data-prepr-origin");
738
722
  const id = element.getAttribute("data-prepr-id") ?? void 0;
739
723
  const field = element.getAttribute("data-prepr-field") ?? void 0;
740
- const locale = element.getAttribute("data-prepr-locale") ?? void 0;
741
- debug5.log("showing overlay", { href, origin });
724
+ debug4.log("showing overlay", { href, origin });
742
725
  const overlay = this.overlay;
743
726
  overlay.style.display = "block";
744
727
  overlay.style.top = `${rect.top + window.scrollY - 2}px`;
@@ -770,7 +753,7 @@ var StegaOverlay = class {
770
753
  tooltip.style.left = `${left}px`;
771
754
  });
772
755
  tooltip.onclick = () => {
773
- this.onEdit({ href, origin, id, field, locale });
756
+ this.onEdit({ href, origin, id, field });
774
757
  };
775
758
  }
776
759
  this.currentElement = element;
@@ -810,7 +793,7 @@ var StegaOverlay = class {
810
793
  };
811
794
 
812
795
  // src/core/stega/proximity.ts
813
- var debug6 = createScopedLogger("stega:proximity");
796
+ var debug5 = createScopedLogger("stega:proximity");
814
797
  var StegaProximity = class {
815
798
  constructor() {
816
799
  this.highlighted = /* @__PURE__ */ new Set();
@@ -845,9 +828,9 @@ var StegaProximity = class {
845
828
  );
846
829
  const nodes = this.getEncodedElements();
847
830
  nodes.forEach((el) => this.observer.observe(el));
848
- debug6.log("observing", nodes.length, "encoded elements");
831
+ debug5.log("observing", nodes.length, "encoded elements");
849
832
  } catch (error) {
850
- debug6.log("error setting up IntersectionObserver", error);
833
+ debug5.log("error setting up IntersectionObserver", error);
851
834
  }
852
835
  }
853
836
  stopObserving() {
@@ -917,7 +900,7 @@ function removeStegaStyles() {
917
900
  }
918
901
 
919
902
  // src/core/stega/scan.ts
920
- var debug7 = createScopedLogger("stega:scan");
903
+ var debug6 = createScopedLogger("stega:scan");
921
904
  var EDITOR_MODE_STYLES = "[data-prepr-encoded]{cursor:pointer}";
922
905
  function createStegaController(opts) {
923
906
  const tooltipEnabled = opts.tooltip ?? true;
@@ -950,13 +933,12 @@ function createStegaController(opts) {
950
933
  href: encoded.getAttribute("data-prepr-href") ?? "",
951
934
  origin: encoded.getAttribute("data-prepr-origin") ?? "",
952
935
  id: encoded.getAttribute("data-prepr-id") ?? void 0,
953
- field: encoded.getAttribute("data-prepr-field") ?? void 0,
954
- locale: encoded.getAttribute("data-prepr-locale") ?? void 0
936
+ field: encoded.getAttribute("data-prepr-field") ?? void 0
955
937
  });
956
938
  };
957
939
  const start = () => {
958
940
  if (initialized) {
959
- debug7.log("already initialized, skipping setup");
941
+ debug6.log("already initialized, skipping setup");
960
942
  return;
961
943
  }
962
944
  if (!tooltipEnabled) {
@@ -966,7 +948,7 @@ function createStegaController(opts) {
966
948
  });
967
949
  document.addEventListener("click", handleClick, { capture: true });
968
950
  initialized = true;
969
- debug7.log("stega controller started (editor mode)");
951
+ debug6.log("stega controller started (editor mode)");
970
952
  return;
971
953
  }
972
954
  injectStegaStyles();
@@ -997,7 +979,7 @@ function createStegaController(opts) {
997
979
  passive: true
998
980
  });
999
981
  initialized = true;
1000
- debug7.log("stega controller started");
982
+ debug6.log("stega controller started");
1001
983
  };
1002
984
  const stop = () => {
1003
985
  if (!initialized) return;
@@ -1006,7 +988,7 @@ function createStegaController(opts) {
1006
988
  elements.cleanup();
1007
989
  removeStegaStyles();
1008
990
  initialized = false;
1009
- debug7.log("stega controller stopped (editor mode)");
991
+ debug6.log("stega controller stopped (editor mode)");
1010
992
  return;
1011
993
  }
1012
994
  if (throttledMouseMove) {
@@ -1026,43 +1008,11 @@ function createStegaController(opts) {
1026
1008
  elements.cleanup();
1027
1009
  removeStegaStyles();
1028
1010
  initialized = false;
1029
- debug7.log("stega controller stopped");
1011
+ debug6.log("stega controller stopped");
1030
1012
  };
1031
1013
  return { start, stop };
1032
1014
  }
1033
1015
 
1034
- // src/core/stega/scroll-to-field.ts
1035
- var debug8 = createScopedLogger("stega:scroll");
1036
- var FLASH_DURATION_MS = 1200;
1037
- var flashTimeout = null;
1038
- function scrollToField(request) {
1039
- const { field, id } = request;
1040
- if (!field) return false;
1041
- const candidates = document.querySelectorAll(
1042
- "[data-prepr-encoded]"
1043
- );
1044
- let target = null;
1045
- for (const candidate of candidates) {
1046
- if (candidate.getAttribute("data-prepr-field") !== field) continue;
1047
- if (id && candidate.getAttribute("data-prepr-id") !== id) continue;
1048
- target = candidate;
1049
- break;
1050
- }
1051
- if (!target) {
1052
- debug8.log("no element found for field", field);
1053
- return false;
1054
- }
1055
- target.scrollIntoView({ behavior: "smooth", block: "center" });
1056
- if (flashTimeout) clearTimeout(flashTimeout);
1057
- document.querySelectorAll(".prepr-overlay-active").forEach((el) => el.classList.remove("prepr-overlay-active"));
1058
- target.classList.add("prepr-overlay-active");
1059
- flashTimeout = setTimeout(() => {
1060
- target.classList.remove("prepr-overlay-active");
1061
- flashTimeout = null;
1062
- }, FLASH_DURATION_MS);
1063
- return true;
1064
- }
1065
-
1066
1016
  // src/core/store.ts
1067
1017
  var DEFAULT_STATE = {
1068
1018
  locale: "en",
@@ -2175,7 +2125,7 @@ function definePreprToolbar() {
2175
2125
  }
2176
2126
 
2177
2127
  // src/core/create-preview.ts
2178
- var debug9 = createScopedLogger("create-preview");
2128
+ var debug7 = createScopedLogger("create-preview");
2179
2129
  var controllerStores = /* @__PURE__ */ new WeakMap();
2180
2130
  function defaultNavigation() {
2181
2131
  return {
@@ -2217,7 +2167,7 @@ function createPreprPreview(opts = {}) {
2217
2167
  const search = new URLSearchParams(window.location.search);
2218
2168
  const hideBar = search.get(PARAM_HIDE_BAR) === "true";
2219
2169
  if (hideBar) {
2220
- debug9.log(`${PARAM_HIDE_BAR}=true \u2014 no visible toolbar`);
2170
+ debug7.log(`${PARAM_HIDE_BAR}=true \u2014 no visible toolbar`);
2221
2171
  }
2222
2172
  const mountUi = (options?.ui ?? true) && !isIframe && !hideBar;
2223
2173
  const locale = resolveLocale(options);
@@ -2253,17 +2203,18 @@ function createPreprPreview(opts = {}) {
2253
2203
  tooltip: mountUi,
2254
2204
  // In the editor, ask the parent to focus the field instead of opening a new
2255
2205
  // tab. Standalone previews keep the window.open behaviour.
2256
- onEdit: ({ href, id, field, locale: contentLocale }) => {
2206
+ onEdit: ({ href, origin, id, field }) => {
2257
2207
  const safeHref = href ? safeEditUrl(href) : null;
2258
2208
  if (href && !safeHref) {
2259
- debug9.warn("ignored edit request with unsupported href scheme");
2209
+ debug7.warn("ignored edit request with unsupported href scheme");
2260
2210
  return;
2261
2211
  }
2262
2212
  if (isIframe) {
2263
2213
  sendPreprEvent("field_edit_requested", {
2214
+ href: safeHref ?? void 0,
2215
+ origin,
2264
2216
  id,
2265
- field,
2266
- locale: contentLocale
2217
+ field
2267
2218
  });
2268
2219
  } else if (safeHref) {
2269
2220
  window.open(safeHref, "_blank", "noopener,noreferrer");
@@ -2271,7 +2222,8 @@ function createPreprPreview(opts = {}) {
2271
2222
  }
2272
2223
  });
2273
2224
  const autoClean = createStegaAutoClean({
2274
- enabled: options?.autoClean
2225
+ enabled: options?.autoClean,
2226
+ isHydrated: opts.isHydrated
2275
2227
  });
2276
2228
  autoClean.start();
2277
2229
  const handleChange = createChangeHandler({
@@ -2290,15 +2242,7 @@ function createPreprPreview(opts = {}) {
2290
2242
  handleChange(before, state);
2291
2243
  });
2292
2244
  const bridge = createIframeBridge(store, {
2293
- allowedEditorOrigins: options?.allowedEditorOrigins,
2294
- // The editor selecting a field scrolls the matching element into view. The
2295
- // lookup relies on the attributes the stega scan writes, so it finds
2296
- // nothing until encoded content has been tagged.
2297
- onScrollToField: ({ field, id }) => {
2298
- if (!scrollToField({ field, id })) {
2299
- debug9.log("no element on this page renders field", field);
2300
- }
2301
- }
2245
+ allowedEditorOrigins: options?.allowedEditorOrigins
2302
2246
  });
2303
2247
  sendPreprEvent(
2304
2248
  "getScrollPosition",
@@ -2318,15 +2262,34 @@ function createPreprPreview(opts = {}) {
2318
2262
  bridge.stop();
2319
2263
  }
2320
2264
  el?.remove();
2321
- debug9.log("preview destroyed");
2265
+ debug7.log("preview destroyed");
2322
2266
  }
2323
2267
  const controller = { destroy };
2324
2268
  controllerStores.set(controller, store);
2325
2269
  return controller;
2326
2270
  }
2327
2271
 
2272
+ // src/react/hydration.ts
2273
+ var PROPS_KEY_PREFIX = "__reactProps$";
2274
+ function reactProps(element) {
2275
+ for (const key of Object.keys(element)) {
2276
+ if (key.startsWith(PROPS_KEY_PREFIX)) {
2277
+ return element[key];
2278
+ }
2279
+ }
2280
+ return void 0;
2281
+ }
2282
+ function isReactHydrated(text) {
2283
+ for (let el = text.parentElement; el; el = el.parentElement) {
2284
+ const props = reactProps(el);
2285
+ if (!props) continue;
2286
+ return el === text.parentElement || props.dangerouslySetInnerHTML != null;
2287
+ }
2288
+ return true;
2289
+ }
2290
+
2328
2291
  // src/version.ts
2329
- var VERSION = "0.5.0-rc.2";
2292
+ var VERSION = "0.6.0-alpha.1";
2330
2293
 
2331
2294
  // src/core/pixel.ts
2332
2295
  var PIXEL_SCRIPT_URL = "https://cdn.tracking.prepr.io/js/prepr.min.js";
@@ -2374,7 +2337,12 @@ function PreprPreview({
2374
2337
  ...props
2375
2338
  }) {
2376
2339
  useEffect(() => {
2377
- const preview = createPreprPreview({ props, options, navigation });
2340
+ const preview = createPreprPreview({
2341
+ props,
2342
+ options,
2343
+ navigation,
2344
+ isHydrated: isReactHydrated
2345
+ });
2378
2346
  return () => preview.destroy();
2379
2347
  }, []);
2380
2348
  return null;
@@ -0,0 +1,59 @@
1
+ import { a as PreprErrorCode, c as PreprSegment, P as PreprHeaders, b as PreprFeatures, e as PreprToolbarProps } from './types-BYH9XqmN.js';
2
+
3
+ /**
4
+ * Whether a Prepr GraphQL response differs per visitor, read from its `Vary`
5
+ * header. Use it when fetching with your own GraphQL client; the toolkit's
6
+ * `createPreprClient` already returns it as `personalized`.
7
+ *
8
+ * Never throws; a response without `Vary` is not personalized.
9
+ */
10
+ declare function isPersonalizedResponse(source: Response | Headers): boolean;
11
+
12
+ /** Error thrown by the token helpers and the segments fetch. */
13
+ declare class PreprError extends Error {
14
+ readonly code: PreprErrorCode;
15
+ readonly context?: string | undefined;
16
+ readonly originalError?: Error | undefined;
17
+ constructor(message: string, code: PreprErrorCode, context?: string | undefined, originalError?: Error | undefined);
18
+ }
19
+ /** Prepr Customer ID, or null if the middleware did not run for this request. */
20
+ declare function getPreprUUIDFromHeaders(headers: Headers): string | null;
21
+ /** Active segment ID, or null when no segment is selected. */
22
+ declare function getActiveSegmentFromHeaders(headers: Headers): string | null;
23
+ /** Active A/B variant (`'A'`/`'B'`), or null when no test is running. */
24
+ declare function getActiveVariantFromHeaders(headers: Headers): string | null;
25
+ /**
26
+ * Collect the Prepr headers into a `PreprHeaders`. Lookups are case-insensitive
27
+ * per the Fetch spec, but the returned keys use the declared casing.
28
+ */
29
+ declare function getPreprHeadersFromHeaders(headers: Headers): PreprHeaders;
30
+ /**
31
+ * Asserts `token` is the full `https://graphql.prepr.io/<token>` URL.
32
+ * @throws PreprError if the token is missing or not an HTTPS URL.
33
+ */
34
+ declare function validatePreprToken(token: string): void;
35
+ /**
36
+ * Pulls the access token out of a GraphQL URL: `https://graphql.prepr.io/abc123`
37
+ * -> `abc123`.
38
+ * @throws PreprError if the URL is not a valid `graphql.prepr.io` URL.
39
+ */
40
+ declare function extractAccessToken(graphqlUrl: string): string;
41
+ /**
42
+ * Fetches the environment's segments from the Prepr API.
43
+ *
44
+ * `token` must be a Prepr GraphQL URL with scope 'segments'.
45
+ * @throws PreprError if the token is invalid or the request fails.
46
+ */
47
+ declare function getPreprEnvironmentSegments(token: string): Promise<PreprSegment[]>;
48
+ /**
49
+ * Never throws: a failed segments fetch yields an empty list so the toolbar
50
+ * still renders. The list is returned under both `segments` and the deprecated
51
+ * `data` alias.
52
+ *
53
+ * Pass the same `features` object you give the toolbar and the middleware: with
54
+ * segments disabled the `_Segments` request is skipped entirely, saving a
55
+ * round-trip per page.
56
+ */
57
+ declare function getToolbarPropsFromHeaders(headers: Headers, token: string, features?: PreprFeatures): Promise<PreprToolbarProps>;
58
+
59
+ export { PreprError as P, getActiveVariantFromHeaders as a, getPreprEnvironmentSegments as b, getPreprHeadersFromHeaders as c, getPreprUUIDFromHeaders as d, extractAccessToken as e, getToolbarPropsFromHeaders as f, getActiveSegmentFromHeaders as g, isPersonalizedResponse as i, validatePreprToken as v };
@@ -0,0 +1,59 @@
1
+ import { a as PreprErrorCode, c as PreprSegment, P as PreprHeaders, b as PreprFeatures, e as PreprToolbarProps } from './types-BYH9XqmN.cjs';
2
+
3
+ /**
4
+ * Whether a Prepr GraphQL response differs per visitor, read from its `Vary`
5
+ * header. Use it when fetching with your own GraphQL client; the toolkit's
6
+ * `createPreprClient` already returns it as `personalized`.
7
+ *
8
+ * Never throws; a response without `Vary` is not personalized.
9
+ */
10
+ declare function isPersonalizedResponse(source: Response | Headers): boolean;
11
+
12
+ /** Error thrown by the token helpers and the segments fetch. */
13
+ declare class PreprError extends Error {
14
+ readonly code: PreprErrorCode;
15
+ readonly context?: string | undefined;
16
+ readonly originalError?: Error | undefined;
17
+ constructor(message: string, code: PreprErrorCode, context?: string | undefined, originalError?: Error | undefined);
18
+ }
19
+ /** Prepr Customer ID, or null if the middleware did not run for this request. */
20
+ declare function getPreprUUIDFromHeaders(headers: Headers): string | null;
21
+ /** Active segment ID, or null when no segment is selected. */
22
+ declare function getActiveSegmentFromHeaders(headers: Headers): string | null;
23
+ /** Active A/B variant (`'A'`/`'B'`), or null when no test is running. */
24
+ declare function getActiveVariantFromHeaders(headers: Headers): string | null;
25
+ /**
26
+ * Collect the Prepr headers into a `PreprHeaders`. Lookups are case-insensitive
27
+ * per the Fetch spec, but the returned keys use the declared casing.
28
+ */
29
+ declare function getPreprHeadersFromHeaders(headers: Headers): PreprHeaders;
30
+ /**
31
+ * Asserts `token` is the full `https://graphql.prepr.io/<token>` URL.
32
+ * @throws PreprError if the token is missing or not an HTTPS URL.
33
+ */
34
+ declare function validatePreprToken(token: string): void;
35
+ /**
36
+ * Pulls the access token out of a GraphQL URL: `https://graphql.prepr.io/abc123`
37
+ * -> `abc123`.
38
+ * @throws PreprError if the URL is not a valid `graphql.prepr.io` URL.
39
+ */
40
+ declare function extractAccessToken(graphqlUrl: string): string;
41
+ /**
42
+ * Fetches the environment's segments from the Prepr API.
43
+ *
44
+ * `token` must be a Prepr GraphQL URL with scope 'segments'.
45
+ * @throws PreprError if the token is invalid or the request fails.
46
+ */
47
+ declare function getPreprEnvironmentSegments(token: string): Promise<PreprSegment[]>;
48
+ /**
49
+ * Never throws: a failed segments fetch yields an empty list so the toolbar
50
+ * still renders. The list is returned under both `segments` and the deprecated
51
+ * `data` alias.
52
+ *
53
+ * Pass the same `features` object you give the toolbar and the middleware: with
54
+ * segments disabled the `_Segments` request is skipped entirely, saving a
55
+ * round-trip per page.
56
+ */
57
+ declare function getToolbarPropsFromHeaders(headers: Headers, token: string, features?: PreprFeatures): Promise<PreprToolbarProps>;
58
+
59
+ export { PreprError as P, getActiveVariantFromHeaders as a, getPreprEnvironmentSegments as b, getPreprHeadersFromHeaders as c, getPreprUUIDFromHeaders as d, extractAccessToken as e, getToolbarPropsFromHeaders as f, getActiveSegmentFromHeaders as g, isPersonalizedResponse as i, validatePreprToken as v };