@preprio/toolkit 0.6.0-alpha.6 → 0.6.0-alpha.7

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 (52) hide show
  1. package/README.md +138 -52
  2. package/dist/astro/{chunk-MWRA3732.js → chunk-3EXK2LHI.js} +1 -1
  3. package/dist/astro/graphql.cjs +58 -10
  4. package/dist/astro/graphql.d.cts +33 -38
  5. package/dist/astro/graphql.d.ts +33 -38
  6. package/dist/astro/graphql.js +59 -11
  7. package/dist/astro/index.cjs +1 -1
  8. package/dist/astro/index.js +1 -1
  9. package/dist/{chunk-OQGEL4TV.js → chunk-3QGWCYTL.js} +1 -1
  10. package/dist/{chunk-W6AVX6FI.js → chunk-PFJO4TYL.js} +52 -5
  11. package/dist/{chunk-Y7DWVOOY.js → chunk-U7TRFVK7.js} +1 -1
  12. package/dist/{chunk-NSQOGI5K.js → chunk-VQIPTUN4.js} +1 -1
  13. package/dist/graphql/codegen.cjs +51 -4
  14. package/dist/graphql/codegen.js +3 -3
  15. package/dist/graphql.cjs +89 -10
  16. package/dist/graphql.d.cts +69 -39
  17. package/dist/graphql.d.ts +69 -39
  18. package/dist/graphql.js +38 -9
  19. package/dist/index.cjs +1 -1
  20. package/dist/index.d.cts +1 -1
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.js +3 -3
  23. package/dist/nextjs/{chunk-PIQF5ZI5.js → chunk-CZK7SMBD.js} +58 -53
  24. package/dist/nextjs/components.cjs +175 -242
  25. package/dist/nextjs/components.d.cts +25 -21
  26. package/dist/nextjs/components.d.ts +25 -21
  27. package/dist/nextjs/components.js +167 -236
  28. package/dist/nextjs/graphql.cjs +372 -509
  29. package/dist/nextjs/graphql.d.cts +131 -135
  30. package/dist/nextjs/graphql.d.ts +131 -135
  31. package/dist/nextjs/graphql.js +307 -456
  32. package/dist/nextjs/index.cjs +114 -54
  33. package/dist/nextjs/index.d.cts +57 -33
  34. package/dist/nextjs/index.d.ts +57 -33
  35. package/dist/nextjs/index.js +108 -5
  36. package/dist/nuxt/{chunk-PF6GVWHV.js → chunk-JL33ENCM.js} +1 -1
  37. package/dist/nuxt/graphql.cjs +58 -10
  38. package/dist/nuxt/graphql.d.cts +33 -38
  39. package/dist/nuxt/graphql.d.ts +33 -38
  40. package/dist/nuxt/graphql.js +59 -11
  41. package/dist/nuxt/index.cjs +1 -1
  42. package/dist/nuxt/index.js +1 -1
  43. package/dist/react/components.cjs +1 -1
  44. package/dist/react/components.js +1 -1
  45. package/dist/sveltekit/{chunk-5K7HWMIF.js → chunk-S7S276V7.js} +1 -1
  46. package/dist/sveltekit/graphql.cjs +58 -10
  47. package/dist/sveltekit/graphql.d.cts +33 -38
  48. package/dist/sveltekit/graphql.d.ts +33 -38
  49. package/dist/sveltekit/graphql.js +59 -11
  50. package/dist/sveltekit/index.cjs +1 -1
  51. package/dist/sveltekit/index.js +1 -1
  52. package/package.json +1 -1
@@ -2,7 +2,7 @@
2
2
  import { NextResponse } from "next/server";
3
3
 
4
4
  // src/version.ts
5
- var VERSION = "0.6.0-alpha.6";
5
+ var VERSION = "0.6.0-alpha.7";
6
6
 
7
7
  // src/core/constants.ts
8
8
  var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
@@ -426,55 +426,6 @@ function isPersonalizedResponse(source) {
426
426
  return PERSONALIZED_VARY.test(headers2.get("vary") ?? "");
427
427
  }
428
428
 
429
- // src/nextjs/bucket.ts
430
- var PREPR_BUCKET_COOKIE = "prepr_ab_bucket";
431
- var isBucket = (value) => value === "A" || value === "B";
432
- function setBucketCookie(request, response, bucket) {
433
- response.cookies.set(PREPR_BUCKET_COOKIE, bucket, {
434
- path: "/",
435
- sameSite: "lax",
436
- // Readable on purpose: PreprStack's inline switch reads it before first
437
- // paint. It holds only "A" or "B".
438
- httpOnly: false,
439
- secure: request.nextUrl.protocol === "https:",
440
- maxAge: 60 * 60 * 24 * 365
441
- });
442
- }
443
- async function resolvePreprBucket(request, response, options) {
444
- const stored = request.cookies.get(PREPR_BUCKET_COOKIE)?.value;
445
- const forced = response.headers.get("x-middleware-request-prepr-abtesting") ?? request.cookies.get(COOKIE_VARIANT)?.value;
446
- if (options.preview && isBucket(forced)) {
447
- if (forced !== stored) setBucketCookie(request, response, forced);
448
- return forced;
449
- }
450
- if (isBucket(stored)) return stored;
451
- if (options.onlyIf) {
452
- const needed = await Promise.resolve().then(options.onlyIf).catch(() => true);
453
- if (!needed) return "A";
454
- }
455
- const customerId = request.cookies.get(COOKIE_UID)?.value ?? response.cookies.get(COOKIE_UID)?.value;
456
- if (!customerId) return "A";
457
- try {
458
- const res = await fetch(options.url, {
459
- method: "POST",
460
- headers: {
461
- "Content-Type": "application/json",
462
- "Prepr-Customer-Id": customerId
463
- },
464
- body: JSON.stringify({ query: "{ __typename }" }),
465
- cache: "no-store",
466
- signal: AbortSignal.timeout(options.timeoutMs ?? 1500)
467
- });
468
- const bucket = res.headers.get("x-prepr-customer-bucket");
469
- if (isBucket(bucket)) {
470
- setBucketCookie(request, response, bucket);
471
- return bucket;
472
- }
473
- } catch {
474
- }
475
- return "A";
476
- }
477
-
478
429
  // src/nextjs/draft.ts
479
430
  import { cookies, draftMode } from "next/headers";
480
431
  import { redirect } from "next/navigation";
@@ -525,6 +476,56 @@ function createPreprDraftRoute(options) {
525
476
  };
526
477
  }
527
478
 
479
+ // src/core/graphql/cache-tags.ts
480
+ var MAX_TAGS = 128;
481
+ var MAX_TAG_LENGTH = 256;
482
+ var PREPR_ANY_ITEM_TAG = "prepr:any";
483
+ var PREPR_SCHEMA_TAG = "prepr:schema";
484
+ var preprItemTag = (id) => `prepr:${id}`;
485
+ var NOT_ALPHANUMERIC = /[^a-z0-9]/g;
486
+ var preprModelTag = (model) => `prepr:model:${model.toLowerCase().replace(NOT_ALPHANUMERIC, "")}`;
487
+ function preprModelTagsFor(document, data, schema) {
488
+ const operation = document.definitions.find(
489
+ (definition) => definition.kind === "OperationDefinition" && definition.operation === "query"
490
+ );
491
+ const root = schema.types[schema.queryType];
492
+ if (!operation || !root) return [];
493
+ const models = (type) => {
494
+ const members = schema.types[type]?.possibleTypes ?? [];
495
+ return members.length > 0 ? members : [type];
496
+ };
497
+ const tags = /* @__PURE__ */ new Set();
498
+ for (const selection of operation.selectionSet.selections) {
499
+ if (selection.kind !== "Field") continue;
500
+ const type = root.fields[selection.name.value]?.type;
501
+ const info = type ? schema.types[type] : void 0;
502
+ if (!type || !info) continue;
503
+ const items = info.fields.items?.type;
504
+ const key = (selection.alias ?? selection.name).value;
505
+ const found = typeof data === "object" && data !== null && data[key] != null;
506
+ const affected = items ? models(items) : found ? [] : models(type);
507
+ for (const model of affected) tags.add(preprModelTag(model));
508
+ }
509
+ return [...tags];
510
+ }
511
+ function preprCacheTags(data) {
512
+ const tags = /* @__PURE__ */ new Set();
513
+ const stack = [data];
514
+ while (stack.length > 0) {
515
+ const value = stack.pop();
516
+ if (typeof value !== "object" || value === null) continue;
517
+ const id = value._id;
518
+ if (typeof id === "string" && id !== "") {
519
+ const tag = preprItemTag(id);
520
+ if (tag.length > MAX_TAG_LENGTH) return [PREPR_ANY_ITEM_TAG];
521
+ tags.add(tag);
522
+ if (tags.size > MAX_TAGS) return [PREPR_ANY_ITEM_TAG];
523
+ }
524
+ for (const child of Object.values(value)) stack.push(child);
525
+ }
526
+ return [...tags];
527
+ }
528
+
528
529
  export {
529
530
  createPreprMiddleware,
530
531
  PreprError,
@@ -537,8 +538,12 @@ export {
537
538
  getPreprHeaders,
538
539
  getToolbarProps,
539
540
  isPersonalizedResponse,
540
- PREPR_BUCKET_COOKIE,
541
- resolvePreprBucket,
542
541
  preprDraftRedirect,
543
- createPreprDraftRoute
542
+ createPreprDraftRoute,
543
+ PREPR_ANY_ITEM_TAG,
544
+ PREPR_SCHEMA_TAG,
545
+ preprItemTag,
546
+ preprModelTag,
547
+ preprModelTagsFor,
548
+ preprCacheTags
544
549
  };
@@ -21,7 +21,7 @@ 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
+ PreprDecisionApplier: () => PreprDecisionApplier,
25
25
  PreprPersonalizationSwitch: () => PreprPersonalizationSwitch,
26
26
  PreprToolbar: () => PreprToolbar,
27
27
  PreprTrackingPixel: () => PreprTrackingPixel,
@@ -458,11 +458,11 @@ function createStegaAutoClean(options) {
458
458
  const settled = stripText ? info?.cleaned : info?.original;
459
459
  if (info && textContent === settled) {
460
460
  if (!info.decoded) return false;
461
- const shown = resolveEditTarget(textNode);
462
- if (!shown) return false;
461
+ const shown2 = resolveEditTarget(textNode);
462
+ if (!shown2) return false;
463
463
  pendingHidden.delete(textNode);
464
- if (shown.hasAttribute("data-prepr-encoded")) return false;
465
- tagEncodedElement(shown, info.decoded);
464
+ if (shown2.hasAttribute("data-prepr-encoded")) return false;
465
+ tagEncodedElement(shown2, info.decoded);
466
466
  return true;
467
467
  }
468
468
  try {
@@ -1055,7 +1055,7 @@ var DEFAULT_STATE = {
1055
1055
  };
1056
1056
  function createToolbarStore(initial = {}) {
1057
1057
  let state = { ...DEFAULT_STATE, ...initial };
1058
- const listeners = /* @__PURE__ */ new Set();
1058
+ const listeners2 = /* @__PURE__ */ new Set();
1059
1059
  function get() {
1060
1060
  return state;
1061
1061
  }
@@ -1064,14 +1064,14 @@ function createToolbarStore(initial = {}) {
1064
1064
  const hasChange = keys.some((key) => patch[key] !== state[key]);
1065
1065
  if (!hasChange) return;
1066
1066
  state = { ...state, ...patch };
1067
- for (const listener of listeners) {
1067
+ for (const listener of listeners2) {
1068
1068
  listener(state);
1069
1069
  }
1070
1070
  }
1071
1071
  function subscribe(fn) {
1072
- listeners.add(fn);
1072
+ listeners2.add(fn);
1073
1073
  return () => {
1074
- listeners.delete(fn);
1074
+ listeners2.delete(fn);
1075
1075
  };
1076
1076
  }
1077
1077
  return { get, set, subscribe };
@@ -2316,204 +2316,42 @@ function isReactHydrated(text) {
2316
2316
  return true;
2317
2317
  }
2318
2318
 
2319
- // src/core/refresh.ts
2320
- var PREPR_REFRESH_COOKIE = "prepr_refresh";
2321
- var PREPR_REFRESH_HEADER = "Prepr-Refresh";
2322
- var REFRESH_MAX_PATHS = 5;
2323
- var SAFE_PATH = /^\/(?![/\\])[\w\-./%]{0,200}$/;
2324
- function safePath(path) {
2325
- return SAFE_PATH.test(path) ? path : void 0;
2326
- }
2327
- function isFresh(at, freshFor, now = Date.now()) {
2328
- const age = now - at;
2329
- return at > 0 && age >= 0 && age < freshFor;
2330
- }
2331
- function cleanEntries(entries) {
2332
- const out = [];
2333
- for (const entry of entries) {
2334
- if (out.length === REFRESH_MAX_PATHS) break;
2335
- if (safePath(entry.path) && !out.some((e2) => e2.path === entry.path)) {
2336
- out.push(entry);
2337
- }
2338
- }
2339
- return out;
2340
- }
2341
- function parseRefresh(raw) {
2342
- return cleanEntries(
2343
- (raw ?? "").split("|").map((pair) => {
2344
- const split = pair.lastIndexOf("~");
2345
- const at = Number(split < 0 ? "" : pair.slice(split + 1));
2346
- return {
2347
- path: split < 0 ? pair : pair.slice(0, split),
2348
- at: Number.isFinite(at) && at > 0 ? Math.floor(at) : 0
2349
- };
2350
- })
2351
- );
2352
- }
2353
-
2354
- // src/core/refresh-scheduler.ts
2355
- var VIEW_DELAYS_MS = [3e3, 1e4];
2356
- var INTENT_MIN_AGE_MS = 2e3;
2357
- var INTENT_EVENTS = ["pointerover", "focusin", "touchstart"];
2358
- var LISTENER = { capture: true, passive: true };
2359
- var COOKIE_PATTERN = new RegExp(`(?:^|; )${PREPR_REFRESH_COOKIE}=([^;]*)`);
2360
- var UNSUPPORTED_KEY = "prepr_refresh_unsupported";
2361
- function readEntries() {
2362
- const match = document.cookie.match(COOKIE_PATTERN);
2363
- try {
2364
- return parseRefresh(match ? decodeURIComponent(match[1]) : void 0);
2365
- } catch {
2366
- return [];
2367
- }
2368
- }
2369
- function unsupported() {
2370
- try {
2371
- return sessionStorage.getItem(UNSUPPORTED_KEY) === "1";
2372
- } catch {
2373
- return false;
2374
- }
2375
- }
2376
- function createPreprRefreshScheduler(options = {}) {
2377
- const freshFor = options.freshFor ?? 1e4;
2378
- let timers = [];
2379
- let inFlight = false;
2380
- let stopped = unsupported();
2381
- function refresh() {
2382
- if (inFlight || stopped) return;
2383
- const paths = readEntries().map((entry) => entry.path);
2384
- if (paths.length === 0) return;
2385
- inFlight = true;
2386
- const controller = new AbortController();
2387
- fetch(location.pathname, {
2388
- headers: { [PREPR_REFRESH_HEADER]: paths.join("|") },
2389
- cache: "no-store",
2390
- credentials: "same-origin",
2391
- signal: controller.signal
2392
- }).then((response) => {
2393
- if (response.status === 204) return;
2394
- stopped = true;
2395
- controller.abort();
2396
- try {
2397
- sessionStorage.setItem(UNSUPPORTED_KEY, "1");
2398
- } catch {
2399
- }
2400
- }).catch(() => void 0).finally(() => {
2401
- inFlight = false;
2402
- });
2403
- }
2404
- function onIntent(event) {
2405
- const anchor = event.target?.closest?.("a[href]");
2406
- if (!(anchor instanceof HTMLAnchorElement)) return;
2407
- let url;
2408
- try {
2409
- url = new URL(anchor.href, location.href);
2410
- } catch {
2411
- return;
2412
- }
2413
- if (url.origin !== location.origin) return;
2414
- const entry = readEntries().find((e2) => e2.path === url.pathname);
2415
- if (entry && !isFresh(entry.at, INTENT_MIN_AGE_MS)) refresh();
2416
- }
2417
- function onVisibility() {
2418
- if (document.visibilityState === "visible" && readEntries().some((entry) => !isFresh(entry.at, freshFor))) {
2419
- refresh();
2420
- }
2421
- }
2422
- for (const type of INTENT_EVENTS) {
2423
- document.addEventListener(type, onIntent, LISTENER);
2424
- }
2425
- document.addEventListener("visibilitychange", onVisibility, {
2426
- passive: true
2427
- });
2428
- return {
2429
- view() {
2430
- timers.forEach(clearTimeout);
2431
- timers = VIEW_DELAYS_MS.map((delay) => setTimeout(refresh, delay));
2432
- },
2433
- stop() {
2434
- timers.forEach(clearTimeout);
2435
- timers = [];
2436
- for (const type of INTENT_EVENTS) {
2437
- document.removeEventListener(type, onIntent, LISTENER);
2438
- }
2439
- document.removeEventListener("visibilitychange", onVisibility);
2440
- }
2441
- };
2442
- }
2443
-
2444
- // src/nextjs/ab-switch.ts
2445
- function applyPreprAbBucket() {
2446
- const match = document.cookie.match(/(?:^|; )prepr_ab_bucket=([AB])(?:;|$)/);
2447
- const bucket = match ? match[1] : "A";
2319
+ // src/nextjs/decision-switch.ts
2320
+ function applyPreprDecisions(decision) {
2448
2321
  const state = window;
2449
- if (state.__preprAbBucket === bucket) return;
2450
- state.__preprAbBucket = bucket;
2451
- const css = bucket === "A" ? "" : '[data-prepr-ab="A"]{display:none!important}[data-prepr-ab="' + bucket + '"]{display:contents!important}';
2452
- try {
2453
- if (!state.__preprAbSheet) {
2454
- const sheet = new CSSStyleSheet();
2455
- document.adoptedStyleSheets = document.adoptedStyleSheets.concat(sheet);
2456
- state.__preprAbSheet = sheet;
2457
- }
2458
- state.__preprAbSheet.replaceSync(css);
2459
- } catch {
2460
- if (!state.__preprAbSheet) {
2461
- state.__preprAbSheet = document.createElement("style");
2462
- document.head.appendChild(state.__preprAbSheet);
2463
- }
2464
- state.__preprAbSheet.textContent = css;
2465
- }
2466
- }
2467
- function applyPreprDecisions(view) {
2468
- const viewState = window;
2469
- const current = view === void 0 ? location.pathname + location.search : view;
2470
- if (viewState.__preprDecisionsView === current) return;
2471
- viewState.__preprDecisionsView = current;
2472
- const match = document.cookie.match(/(?:^|; )prepr_decisions=([^;]*)/);
2473
- let raw;
2474
- try {
2475
- raw = match ? decodeURIComponent(match[1]) : "";
2476
- } catch {
2477
- raw = "";
2478
- }
2479
- const state = window;
2480
- if (state.__preprDecisions === raw) return;
2481
- state.__preprDecisions = raw;
2482
- const id = /^[\w-]+$/;
2483
- let css = "";
2484
- raw.split("~").forEach((pair) => {
2485
- const at = pair.indexOf(":");
2486
- const group = pair.slice(0, at);
2487
- if (at < 1 || !id.test(group)) return;
2488
- const selector = '[data-prepr-group="' + group + '"]';
2489
- css += selector + "{display:none!important}";
2490
- pair.slice(at + 1).split("|").forEach((variant) => {
2491
- if (id.test(variant)) {
2492
- css += selector + '[data-prepr-variant="' + variant + '"]{display:contents!important}';
2493
- }
2322
+ const key = location.pathname + location.search;
2323
+ if (decision !== void 0) state.__preprDecision = { key, value: decision };
2324
+ const current = state.__preprDecision;
2325
+ if (!current || current.key !== key) return false;
2326
+ document.querySelectorAll("[data-prepr-decision]").forEach((marker) => {
2327
+ marker.setAttribute("data-prepr-decided", "");
2328
+ });
2329
+ const defaults = current.value === "*";
2330
+ const chosen = {};
2331
+ current.value.split("~").forEach((pair) => {
2332
+ const colon = pair.indexOf(":");
2333
+ if (colon > 0)
2334
+ chosen[pair.slice(0, colon)] = pair.slice(colon + 1).split("|");
2335
+ });
2336
+ document.querySelectorAll("[data-prepr-v]").forEach((element) => {
2337
+ const nested = element.hasAttribute("data-prepr-nested");
2338
+ const show = defaults ? element.hasAttribute("data-prepr-default") : (element.getAttribute("data-prepr-v") || "").split(" ").every((token) => {
2339
+ const colon = token.indexOf(":");
2340
+ const variant = token.slice(colon + 1);
2341
+ const variants = chosen[token.slice(0, colon)];
2342
+ return !!variants && (nested ? variants[0] === variant : variants.indexOf(variant) >= 0);
2494
2343
  });
2344
+ if (show) element.setAttribute("data-prepr-show", "");
2345
+ else element.removeAttribute("data-prepr-show");
2495
2346
  });
2496
- try {
2497
- if (!state.__preprDecisionSheet) {
2498
- const sheet = new CSSStyleSheet();
2499
- document.adoptedStyleSheets = document.adoptedStyleSheets.concat(sheet);
2500
- state.__preprDecisionSheet = sheet;
2501
- }
2502
- state.__preprDecisionSheet.replaceSync(css);
2503
- } catch {
2504
- if (!state.__preprDecisionSheet) {
2505
- state.__preprDecisionSheet = document.createElement("style");
2506
- document.head.appendChild(state.__preprDecisionSheet);
2507
- }
2508
- state.__preprDecisionSheet.textContent = css;
2509
- }
2347
+ return true;
2510
2348
  }
2511
2349
 
2512
2350
  // src/react/components.ts
2513
2351
  var import_react = require("react");
2514
2352
 
2515
2353
  // src/version.ts
2516
- var VERSION = "0.6.0-alpha.6";
2354
+ var VERSION = "0.6.0-alpha.7";
2517
2355
 
2518
2356
  // src/core/pixel.ts
2519
2357
  var PIXEL_SCRIPT_URL = "https://cdn.tracking.prepr.io/js/prepr.min.js";
@@ -2588,62 +2426,152 @@ function PreprToolbar({
2588
2426
  }, []);
2589
2427
  return null;
2590
2428
  }
2591
- function PreprAbSwitch() {
2592
- (0, import_react2.useLayoutEffect)(() => {
2593
- applyPreprAbBucket();
2429
+ var DECIDE_HEADER = "Prepr-Decide";
2430
+ var DECIDE_TIMEOUT_MS = 6e3;
2431
+ function requestDecision(key) {
2432
+ const pending = { key, value: null };
2433
+ pending.value = fetch(key, {
2434
+ headers: { [DECIDE_HEADER]: "1" },
2435
+ cache: "no-store",
2436
+ credentials: "same-origin",
2437
+ signal: AbortSignal.timeout(DECIDE_TIMEOUT_MS)
2438
+ }).then((response) => response.ok ? response.text() : "*").catch(() => "*").then((value) => {
2439
+ pending.result = value;
2440
+ try {
2441
+ sessionStorage.setItem(
2442
+ "prepr_decision",
2443
+ JSON.stringify({ key, value, at: Date.now() })
2444
+ );
2445
+ } catch {
2446
+ }
2447
+ return value;
2594
2448
  });
2595
- return null;
2449
+ return pending;
2450
+ }
2451
+ var pressed = null;
2452
+ var replaying = null;
2453
+ var shown = /* @__PURE__ */ new Map();
2454
+ var historyStep = false;
2455
+ var onPopState = () => {
2456
+ historyStep = true;
2457
+ };
2458
+ function linkTarget(event) {
2459
+ const anchor = event.target?.closest?.("a[href]");
2460
+ if (!(anchor instanceof HTMLAnchorElement)) return null;
2461
+ if (anchor.target && anchor.target !== "_self" || anchor.hasAttribute("download")) {
2462
+ return null;
2463
+ }
2464
+ const url = new URL(anchor.href, location.href);
2465
+ const key = url.pathname + url.search;
2466
+ if (url.origin !== location.origin || key === location.pathname + location.search) {
2467
+ return null;
2468
+ }
2469
+ return { anchor, key };
2596
2470
  }
2597
- var viewCount = 0;
2598
- function PreprPersonalizationSwitch({
2599
- refresh = true,
2600
- freshFor
2601
- }) {
2602
- const [scheduler, setScheduler] = (0, import_react2.useState)(
2603
- null
2604
- );
2605
- const viewUrl = (0, import_react2.useRef)(null);
2606
- (0, import_react2.useLayoutEffect)(() => {
2607
- const url = location.pathname + location.search;
2608
- if (viewUrl.current === url) return;
2609
- viewUrl.current = url;
2610
- applyPreprDecisions(`${url}#${++viewCount}`);
2471
+ function onPointerDown(event) {
2472
+ if (event.button !== 0) return;
2473
+ const target = linkTarget(event);
2474
+ if (!target || pressed?.key === target.key && pressed.result === void 0) {
2475
+ return;
2476
+ }
2477
+ pressed = requestDecision(target.key);
2478
+ }
2479
+ function onClick(event) {
2480
+ const target = linkTarget(event);
2481
+ if (!target) return;
2482
+ if (replaying === target.anchor) {
2483
+ replaying = null;
2484
+ return;
2485
+ }
2486
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
2487
+ return;
2488
+ }
2489
+ const pending = pressed && pressed.key === target.key ? pressed : requestDecision(target.key);
2490
+ pressed = null;
2491
+ const record = (value) => {
2492
+ window.__preprDecision = { key: target.key, value };
2493
+ };
2494
+ if (pending.result !== void 0) {
2495
+ record(pending.result);
2496
+ return;
2497
+ }
2498
+ event.preventDefault();
2499
+ event.stopImmediatePropagation();
2500
+ void pending.value.then((value) => {
2501
+ record(value);
2502
+ replaying = target.anchor;
2503
+ target.anchor.click();
2611
2504
  });
2505
+ }
2506
+ var listeners = 0;
2507
+ function useDecisionRequests() {
2612
2508
  (0, import_react2.useEffect)(() => {
2613
- if (!refresh) return;
2614
- const started = createPreprRefreshScheduler({ freshFor });
2615
- setScheduler(started);
2509
+ if (listeners++ === 0) {
2510
+ window.addEventListener("pointerdown", onPointerDown, true);
2511
+ window.addEventListener("click", onClick, true);
2512
+ window.addEventListener("popstate", onPopState);
2513
+ }
2616
2514
  return () => {
2617
- started.stop();
2618
- setScheduler(null);
2515
+ if (--listeners === 0) {
2516
+ window.removeEventListener("pointerdown", onPointerDown, true);
2517
+ window.removeEventListener("click", onClick, true);
2518
+ window.removeEventListener("popstate", onPopState);
2519
+ }
2619
2520
  };
2620
- }, [refresh, freshFor]);
2621
- return scheduler ? (0, import_react2.createElement)(
2622
- import_react2.Suspense,
2623
- { fallback: null },
2624
- (0, import_react2.createElement)(ViewCounter, { scheduler })
2625
- ) : null;
2626
- }
2627
- function ViewCounter({
2628
- scheduler
2629
- }) {
2630
- const pathname = (0, import_navigation.usePathname)();
2631
- (0, import_react2.useEffect)(() => {
2632
- scheduler.view();
2633
- }, [pathname, scheduler]);
2521
+ }, []);
2522
+ }
2523
+ function PreprPersonalizationSwitch() {
2524
+ useDecisionRequests();
2525
+ return null;
2526
+ }
2527
+ function PreprDecisionApplier() {
2528
+ useDecisionRequests();
2529
+ (0, import_react2.useLayoutEffect)(() => {
2530
+ const key = location.pathname + location.search;
2531
+ const remember = () => {
2532
+ const state = window.__preprDecision;
2533
+ if (state) shown.set(key, state.value);
2534
+ };
2535
+ const previous = historyStep ? shown.get(key) : void 0;
2536
+ historyStep = false;
2537
+ if (applyPreprDecisions(previous)) {
2538
+ remember();
2539
+ return;
2540
+ }
2541
+ let live = true;
2542
+ const started = window.__preprPending;
2543
+ const value = started?.key === key ? started.value : requestDecision(key).value;
2544
+ void value.then((value2) => {
2545
+ if (live && location.pathname + location.search === key) {
2546
+ applyPreprDecisions(value2);
2547
+ remember();
2548
+ }
2549
+ });
2550
+ return () => {
2551
+ live = false;
2552
+ };
2553
+ });
2634
2554
  return null;
2635
2555
  }
2636
2556
  var VARIANT_KEY_ATTRIBUTE = "data-prepr-variant-key";
2637
2557
  function PreprVariantTracking({
2638
2558
  variantKeys,
2639
- ab,
2640
2559
  children
2641
2560
  }) {
2642
2561
  const ref = (0, import_react2.useRef)(null);
2643
2562
  const keys = variantKeys.join(" ");
2644
2563
  (0, import_react2.useLayoutEffect)(() => {
2645
2564
  let tagged = [];
2646
- const timer = setTimeout(() => {
2565
+ const wrapper = ref.current?.closest("[data-prepr-v]");
2566
+ const shown2 = () => !wrapper || wrapper.hasAttribute("data-prepr-show");
2567
+ const untag = () => {
2568
+ for (const [element] of tagged)
2569
+ element.removeAttribute(VARIANT_KEY_ATTRIBUTE);
2570
+ tagged = [];
2571
+ };
2572
+ const tag = () => {
2573
+ if (!shown2()) return untag();
2574
+ if (tagged.length > 0) return;
2647
2575
  const elements = [...ref.current?.children ?? []];
2648
2576
  const list = keys.split(" ");
2649
2577
  const pairs = elements.length === list.length ? elements.map((element, i2) => [element, list[i2]]) : elements.slice(0, 1).map((element) => [element, list[0]]);
@@ -2652,23 +2580,28 @@ function PreprVariantTracking({
2652
2580
  element.setAttribute(VARIANT_KEY_ATTRIBUTE, key);
2653
2581
  return true;
2654
2582
  });
2583
+ };
2584
+ const timer = setTimeout(tag);
2585
+ const observer = wrapper && new MutationObserver(tag);
2586
+ observer?.observe(wrapper, {
2587
+ attributes: true,
2588
+ attributeFilter: ["data-prepr-show"]
2655
2589
  });
2656
2590
  return () => {
2657
2591
  clearTimeout(timer);
2658
- for (const [element] of tagged) {
2659
- element.removeAttribute(VARIANT_KEY_ATTRIBUTE);
2660
- }
2592
+ observer?.disconnect();
2593
+ untag();
2661
2594
  };
2662
2595
  }, [keys]);
2663
2596
  return (0, import_react2.createElement)(
2664
2597
  "div",
2665
- ab === void 0 ? { ref, style: { display: "contents" } } : { ref, "data-prepr-ab": ab },
2598
+ { ref, style: { display: "contents" } },
2666
2599
  children
2667
2600
  );
2668
2601
  }
2669
2602
  // Annotate the CommonJS export names for ESM import in node:
2670
2603
  0 && (module.exports = {
2671
- PreprAbSwitch,
2604
+ PreprDecisionApplier,
2672
2605
  PreprPersonalizationSwitch,
2673
2606
  PreprToolbar,
2674
2607
  PreprTrackingPixel,