@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
@@ -1,4 +1,4 @@
1
- import { ReactElement, ReactNode } from 'react';
1
+ import { ReactNode, ReactElement } from 'react';
2
2
  import { k as PreprToolbarComponentProps } from '../types-BYH9XqmN.cjs';
3
3
  import { P as PreprPixelConfig } from '../pixel-DwW7cXJm.cjs';
4
4
 
@@ -23,24 +23,30 @@ declare function PreprTrackingPixel({ id, config, }: PreprTrackingPixelProps): n
23
23
  */
24
24
  declare function PreprToolbar({ options, ...props }: PreprToolbarComponentProps): null;
25
25
  /**
26
- * Applies the visitor's A/B bucket after client-side navigation. A full page
27
- * load is covered by the inline script `PreprStack` renders, but React does
28
- * not run inline scripts that arrive with a client-side navigation; a layout
29
- * effect still runs before the browser paints the new page. Rendered by
26
+ * Decides every client-side navigation to another page: when the visitor
27
+ * presses a same-site link, it asks `createPreprProxy` which variants they
28
+ * get on that page, and holds the click until the answer is in. The new page
29
+ * then paints with those variants, never with the default variant first.
30
+ * Pressing starts the request, so the wait mostly overlaps the click itself.
31
+ *
32
+ * Mount it once in the root layout, so links on pages without a
33
+ * `PreprStack` are covered too. Clicks with a modifier key, links to other
34
+ * sites and links that open a new tab or download are left alone.
35
+ *
36
+ * @example
37
+ * <PreprPersonalizationSwitch />
38
+ */
39
+ declare function PreprPersonalizationSwitch(): null;
40
+ /**
41
+ * Applies this page view's decision to the variant wrappers of the
42
+ * `PreprStack` it is rendered by. A click's decision is in before the page
43
+ * paints; Back and Forward show the page as this tab last showed it. On a
44
+ * page load, and any other view without a decision (a navigation started
45
+ * from code, or Back to a page this tab never showed), its personalized
46
+ * sections stay hidden until the decision request answers. Rendered by
30
47
  * `PreprStack`; you do not need it yourself.
31
48
  */
32
- declare function PreprAbSwitch(): null;
33
- interface PreprPersonalizationSwitchProps {
34
- /**
35
- * Keep segment decisions fresh in the background. Default `true`. Leave it
36
- * on for the instance in your root layout; `PreprStack` renders its own
37
- * with `false`.
38
- */
39
- refresh?: boolean;
40
- /** Same as `createPreprProxy`'s `freshFor`. Default 10 000 ms. */
41
- freshFor?: number;
42
- }
43
- declare function PreprPersonalizationSwitch({ refresh, freshFor, }: PreprPersonalizationSwitchProps): ReactElement | null;
49
+ declare function PreprDecisionApplier(): null;
44
50
  /**
45
51
  * Wraps one rendered variant and, once hydrated, puts the variant keys on the
46
52
  * elements inside, where Prepr's tracking pixel counts impressions and the
@@ -55,11 +61,9 @@ declare function PreprPersonalizationSwitch({ refresh, freshFor, }: PreprPersona
55
61
  * carries a variant key is left alone. Rendered by `PreprStack` with
56
62
  * `trackVariants`; you do not need it yourself.
57
63
  */
58
- declare function PreprVariantTracking({ variantKeys, ab, children, }: {
64
+ declare function PreprVariantTracking({ variantKeys, children, }: {
59
65
  variantKeys: string[];
60
- /** The A/B bucket wrapper this also serves as; its CSS decides `display`. */
61
- ab?: string;
62
66
  children?: ReactNode;
63
67
  }): ReactElement;
64
68
 
65
- export { PreprAbSwitch, PreprPersonalizationSwitch, type PreprPersonalizationSwitchProps, PreprToolbar, PreprTrackingPixel, type PreprTrackingPixelProps, PreprVariantTracking };
69
+ export { PreprDecisionApplier, PreprPersonalizationSwitch, PreprToolbar, PreprTrackingPixel, type PreprTrackingPixelProps, PreprVariantTracking };
@@ -1,4 +1,4 @@
1
- import { ReactElement, ReactNode } from 'react';
1
+ import { ReactNode, ReactElement } from 'react';
2
2
  import { k as PreprToolbarComponentProps } from '../types-BYH9XqmN.js';
3
3
  import { P as PreprPixelConfig } from '../pixel-DwW7cXJm.js';
4
4
 
@@ -23,24 +23,30 @@ declare function PreprTrackingPixel({ id, config, }: PreprTrackingPixelProps): n
23
23
  */
24
24
  declare function PreprToolbar({ options, ...props }: PreprToolbarComponentProps): null;
25
25
  /**
26
- * Applies the visitor's A/B bucket after client-side navigation. A full page
27
- * load is covered by the inline script `PreprStack` renders, but React does
28
- * not run inline scripts that arrive with a client-side navigation; a layout
29
- * effect still runs before the browser paints the new page. Rendered by
26
+ * Decides every client-side navigation to another page: when the visitor
27
+ * presses a same-site link, it asks `createPreprProxy` which variants they
28
+ * get on that page, and holds the click until the answer is in. The new page
29
+ * then paints with those variants, never with the default variant first.
30
+ * Pressing starts the request, so the wait mostly overlaps the click itself.
31
+ *
32
+ * Mount it once in the root layout, so links on pages without a
33
+ * `PreprStack` are covered too. Clicks with a modifier key, links to other
34
+ * sites and links that open a new tab or download are left alone.
35
+ *
36
+ * @example
37
+ * <PreprPersonalizationSwitch />
38
+ */
39
+ declare function PreprPersonalizationSwitch(): null;
40
+ /**
41
+ * Applies this page view's decision to the variant wrappers of the
42
+ * `PreprStack` it is rendered by. A click's decision is in before the page
43
+ * paints; Back and Forward show the page as this tab last showed it. On a
44
+ * page load, and any other view without a decision (a navigation started
45
+ * from code, or Back to a page this tab never showed), its personalized
46
+ * sections stay hidden until the decision request answers. Rendered by
30
47
  * `PreprStack`; you do not need it yourself.
31
48
  */
32
- declare function PreprAbSwitch(): null;
33
- interface PreprPersonalizationSwitchProps {
34
- /**
35
- * Keep segment decisions fresh in the background. Default `true`. Leave it
36
- * on for the instance in your root layout; `PreprStack` renders its own
37
- * with `false`.
38
- */
39
- refresh?: boolean;
40
- /** Same as `createPreprProxy`'s `freshFor`. Default 10 000 ms. */
41
- freshFor?: number;
42
- }
43
- declare function PreprPersonalizationSwitch({ refresh, freshFor, }: PreprPersonalizationSwitchProps): ReactElement | null;
49
+ declare function PreprDecisionApplier(): null;
44
50
  /**
45
51
  * Wraps one rendered variant and, once hydrated, puts the variant keys on the
46
52
  * elements inside, where Prepr's tracking pixel counts impressions and the
@@ -55,11 +61,9 @@ declare function PreprPersonalizationSwitch({ refresh, freshFor, }: PreprPersona
55
61
  * carries a variant key is left alone. Rendered by `PreprStack` with
56
62
  * `trackVariants`; you do not need it yourself.
57
63
  */
58
- declare function PreprVariantTracking({ variantKeys, ab, children, }: {
64
+ declare function PreprVariantTracking({ variantKeys, children, }: {
59
65
  variantKeys: string[];
60
- /** The A/B bucket wrapper this also serves as; its CSS decides `display`. */
61
- ab?: string;
62
66
  children?: ReactNode;
63
67
  }): ReactElement;
64
68
 
65
- export { PreprAbSwitch, PreprPersonalizationSwitch, type PreprPersonalizationSwitchProps, PreprToolbar, PreprTrackingPixel, type PreprTrackingPixelProps, PreprVariantTracking };
69
+ export { PreprDecisionApplier, PreprPersonalizationSwitch, PreprToolbar, PreprTrackingPixel, type PreprTrackingPixelProps, PreprVariantTracking };
@@ -2,9 +2,9 @@
2
2
  import {
3
3
  createPreprPreview,
4
4
  loadTrackingPixel
5
- } from "../chunk-Y7DWVOOY.js";
5
+ } from "../chunk-U7TRFVK7.js";
6
6
  import "../chunk-S5RWVMDJ.js";
7
- import "../chunk-NSQOGI5K.js";
7
+ import "../chunk-VQIPTUN4.js";
8
8
 
9
9
  // src/nextjs/components.ts
10
10
  import { usePathname, useRouter } from "next/navigation";
@@ -12,9 +12,7 @@ import {
12
12
  createElement,
13
13
  useEffect as useEffect2,
14
14
  useLayoutEffect,
15
- useRef,
16
- useState,
17
- Suspense
15
+ useRef
18
16
  } from "react";
19
17
 
20
18
  // src/react/hydration.ts
@@ -36,197 +34,35 @@ function isReactHydrated(text) {
36
34
  return true;
37
35
  }
38
36
 
39
- // src/core/refresh.ts
40
- var PREPR_REFRESH_COOKIE = "prepr_refresh";
41
- var PREPR_REFRESH_HEADER = "Prepr-Refresh";
42
- var REFRESH_MAX_PATHS = 5;
43
- var SAFE_PATH = /^\/(?![/\\])[\w\-./%]{0,200}$/;
44
- function safePath(path) {
45
- return SAFE_PATH.test(path) ? path : void 0;
46
- }
47
- function isFresh(at, freshFor, now = Date.now()) {
48
- const age = now - at;
49
- return at > 0 && age >= 0 && age < freshFor;
50
- }
51
- function cleanEntries(entries) {
52
- const out = [];
53
- for (const entry of entries) {
54
- if (out.length === REFRESH_MAX_PATHS) break;
55
- if (safePath(entry.path) && !out.some((e) => e.path === entry.path)) {
56
- out.push(entry);
57
- }
58
- }
59
- return out;
60
- }
61
- function parseRefresh(raw) {
62
- return cleanEntries(
63
- (raw ?? "").split("|").map((pair) => {
64
- const split = pair.lastIndexOf("~");
65
- const at = Number(split < 0 ? "" : pair.slice(split + 1));
66
- return {
67
- path: split < 0 ? pair : pair.slice(0, split),
68
- at: Number.isFinite(at) && at > 0 ? Math.floor(at) : 0
69
- };
70
- })
71
- );
72
- }
73
-
74
- // src/core/refresh-scheduler.ts
75
- var VIEW_DELAYS_MS = [3e3, 1e4];
76
- var INTENT_MIN_AGE_MS = 2e3;
77
- var INTENT_EVENTS = ["pointerover", "focusin", "touchstart"];
78
- var LISTENER = { capture: true, passive: true };
79
- var COOKIE_PATTERN = new RegExp(`(?:^|; )${PREPR_REFRESH_COOKIE}=([^;]*)`);
80
- var UNSUPPORTED_KEY = "prepr_refresh_unsupported";
81
- function readEntries() {
82
- const match = document.cookie.match(COOKIE_PATTERN);
83
- try {
84
- return parseRefresh(match ? decodeURIComponent(match[1]) : void 0);
85
- } catch {
86
- return [];
87
- }
88
- }
89
- function unsupported() {
90
- try {
91
- return sessionStorage.getItem(UNSUPPORTED_KEY) === "1";
92
- } catch {
93
- return false;
94
- }
95
- }
96
- function createPreprRefreshScheduler(options = {}) {
97
- const freshFor = options.freshFor ?? 1e4;
98
- let timers = [];
99
- let inFlight = false;
100
- let stopped = unsupported();
101
- function refresh() {
102
- if (inFlight || stopped) return;
103
- const paths = readEntries().map((entry) => entry.path);
104
- if (paths.length === 0) return;
105
- inFlight = true;
106
- const controller = new AbortController();
107
- fetch(location.pathname, {
108
- headers: { [PREPR_REFRESH_HEADER]: paths.join("|") },
109
- cache: "no-store",
110
- credentials: "same-origin",
111
- signal: controller.signal
112
- }).then((response) => {
113
- if (response.status === 204) return;
114
- stopped = true;
115
- controller.abort();
116
- try {
117
- sessionStorage.setItem(UNSUPPORTED_KEY, "1");
118
- } catch {
119
- }
120
- }).catch(() => void 0).finally(() => {
121
- inFlight = false;
122
- });
123
- }
124
- function onIntent(event) {
125
- const anchor = event.target?.closest?.("a[href]");
126
- if (!(anchor instanceof HTMLAnchorElement)) return;
127
- let url;
128
- try {
129
- url = new URL(anchor.href, location.href);
130
- } catch {
131
- return;
132
- }
133
- if (url.origin !== location.origin) return;
134
- const entry = readEntries().find((e) => e.path === url.pathname);
135
- if (entry && !isFresh(entry.at, INTENT_MIN_AGE_MS)) refresh();
136
- }
137
- function onVisibility() {
138
- if (document.visibilityState === "visible" && readEntries().some((entry) => !isFresh(entry.at, freshFor))) {
139
- refresh();
140
- }
141
- }
142
- for (const type of INTENT_EVENTS) {
143
- document.addEventListener(type, onIntent, LISTENER);
144
- }
145
- document.addEventListener("visibilitychange", onVisibility, {
146
- passive: true
147
- });
148
- return {
149
- view() {
150
- timers.forEach(clearTimeout);
151
- timers = VIEW_DELAYS_MS.map((delay) => setTimeout(refresh, delay));
152
- },
153
- stop() {
154
- timers.forEach(clearTimeout);
155
- timers = [];
156
- for (const type of INTENT_EVENTS) {
157
- document.removeEventListener(type, onIntent, LISTENER);
158
- }
159
- document.removeEventListener("visibilitychange", onVisibility);
160
- }
161
- };
162
- }
163
-
164
- // src/nextjs/ab-switch.ts
165
- function applyPreprAbBucket() {
166
- const match = document.cookie.match(/(?:^|; )prepr_ab_bucket=([AB])(?:;|$)/);
167
- const bucket = match ? match[1] : "A";
37
+ // src/nextjs/decision-switch.ts
38
+ function applyPreprDecisions(decision) {
168
39
  const state = window;
169
- if (state.__preprAbBucket === bucket) return;
170
- state.__preprAbBucket = bucket;
171
- const css = bucket === "A" ? "" : '[data-prepr-ab="A"]{display:none!important}[data-prepr-ab="' + bucket + '"]{display:contents!important}';
172
- try {
173
- if (!state.__preprAbSheet) {
174
- const sheet = new CSSStyleSheet();
175
- document.adoptedStyleSheets = document.adoptedStyleSheets.concat(sheet);
176
- state.__preprAbSheet = sheet;
177
- }
178
- state.__preprAbSheet.replaceSync(css);
179
- } catch {
180
- if (!state.__preprAbSheet) {
181
- state.__preprAbSheet = document.createElement("style");
182
- document.head.appendChild(state.__preprAbSheet);
183
- }
184
- state.__preprAbSheet.textContent = css;
185
- }
186
- }
187
- function applyPreprDecisions(view) {
188
- const viewState = window;
189
- const current = view === void 0 ? location.pathname + location.search : view;
190
- if (viewState.__preprDecisionsView === current) return;
191
- viewState.__preprDecisionsView = current;
192
- const match = document.cookie.match(/(?:^|; )prepr_decisions=([^;]*)/);
193
- let raw;
194
- try {
195
- raw = match ? decodeURIComponent(match[1]) : "";
196
- } catch {
197
- raw = "";
198
- }
199
- const state = window;
200
- if (state.__preprDecisions === raw) return;
201
- state.__preprDecisions = raw;
202
- const id = /^[\w-]+$/;
203
- let css = "";
204
- raw.split("~").forEach((pair) => {
205
- const at = pair.indexOf(":");
206
- const group = pair.slice(0, at);
207
- if (at < 1 || !id.test(group)) return;
208
- const selector = '[data-prepr-group="' + group + '"]';
209
- css += selector + "{display:none!important}";
210
- pair.slice(at + 1).split("|").forEach((variant) => {
211
- if (id.test(variant)) {
212
- css += selector + '[data-prepr-variant="' + variant + '"]{display:contents!important}';
213
- }
40
+ const key = location.pathname + location.search;
41
+ if (decision !== void 0) state.__preprDecision = { key, value: decision };
42
+ const current = state.__preprDecision;
43
+ if (!current || current.key !== key) return false;
44
+ document.querySelectorAll("[data-prepr-decision]").forEach((marker) => {
45
+ marker.setAttribute("data-prepr-decided", "");
46
+ });
47
+ const defaults = current.value === "*";
48
+ const chosen = {};
49
+ current.value.split("~").forEach((pair) => {
50
+ const colon = pair.indexOf(":");
51
+ if (colon > 0)
52
+ chosen[pair.slice(0, colon)] = pair.slice(colon + 1).split("|");
53
+ });
54
+ document.querySelectorAll("[data-prepr-v]").forEach((element) => {
55
+ const nested = element.hasAttribute("data-prepr-nested");
56
+ const show = defaults ? element.hasAttribute("data-prepr-default") : (element.getAttribute("data-prepr-v") || "").split(" ").every((token) => {
57
+ const colon = token.indexOf(":");
58
+ const variant = token.slice(colon + 1);
59
+ const variants = chosen[token.slice(0, colon)];
60
+ return !!variants && (nested ? variants[0] === variant : variants.indexOf(variant) >= 0);
214
61
  });
62
+ if (show) element.setAttribute("data-prepr-show", "");
63
+ else element.removeAttribute("data-prepr-show");
215
64
  });
216
- try {
217
- if (!state.__preprDecisionSheet) {
218
- const sheet = new CSSStyleSheet();
219
- document.adoptedStyleSheets = document.adoptedStyleSheets.concat(sheet);
220
- state.__preprDecisionSheet = sheet;
221
- }
222
- state.__preprDecisionSheet.replaceSync(css);
223
- } catch {
224
- if (!state.__preprDecisionSheet) {
225
- state.__preprDecisionSheet = document.createElement("style");
226
- document.head.appendChild(state.__preprDecisionSheet);
227
- }
228
- state.__preprDecisionSheet.textContent = css;
229
- }
65
+ return true;
230
66
  }
231
67
 
232
68
  // src/react/components.ts
@@ -264,62 +100,152 @@ function PreprToolbar({
264
100
  }, []);
265
101
  return null;
266
102
  }
267
- function PreprAbSwitch() {
268
- useLayoutEffect(() => {
269
- applyPreprAbBucket();
103
+ var DECIDE_HEADER = "Prepr-Decide";
104
+ var DECIDE_TIMEOUT_MS = 6e3;
105
+ function requestDecision(key) {
106
+ const pending = { key, value: null };
107
+ pending.value = fetch(key, {
108
+ headers: { [DECIDE_HEADER]: "1" },
109
+ cache: "no-store",
110
+ credentials: "same-origin",
111
+ signal: AbortSignal.timeout(DECIDE_TIMEOUT_MS)
112
+ }).then((response) => response.ok ? response.text() : "*").catch(() => "*").then((value) => {
113
+ pending.result = value;
114
+ try {
115
+ sessionStorage.setItem(
116
+ "prepr_decision",
117
+ JSON.stringify({ key, value, at: Date.now() })
118
+ );
119
+ } catch {
120
+ }
121
+ return value;
270
122
  });
271
- return null;
123
+ return pending;
272
124
  }
273
- var viewCount = 0;
274
- function PreprPersonalizationSwitch({
275
- refresh = true,
276
- freshFor
277
- }) {
278
- const [scheduler, setScheduler] = useState(
279
- null
280
- );
281
- const viewUrl = useRef(null);
282
- useLayoutEffect(() => {
283
- const url = location.pathname + location.search;
284
- if (viewUrl.current === url) return;
285
- viewUrl.current = url;
286
- applyPreprDecisions(`${url}#${++viewCount}`);
125
+ var pressed = null;
126
+ var replaying = null;
127
+ var shown = /* @__PURE__ */ new Map();
128
+ var historyStep = false;
129
+ var onPopState = () => {
130
+ historyStep = true;
131
+ };
132
+ function linkTarget(event) {
133
+ const anchor = event.target?.closest?.("a[href]");
134
+ if (!(anchor instanceof HTMLAnchorElement)) return null;
135
+ if (anchor.target && anchor.target !== "_self" || anchor.hasAttribute("download")) {
136
+ return null;
137
+ }
138
+ const url = new URL(anchor.href, location.href);
139
+ const key = url.pathname + url.search;
140
+ if (url.origin !== location.origin || key === location.pathname + location.search) {
141
+ return null;
142
+ }
143
+ return { anchor, key };
144
+ }
145
+ function onPointerDown(event) {
146
+ if (event.button !== 0) return;
147
+ const target = linkTarget(event);
148
+ if (!target || pressed?.key === target.key && pressed.result === void 0) {
149
+ return;
150
+ }
151
+ pressed = requestDecision(target.key);
152
+ }
153
+ function onClick(event) {
154
+ const target = linkTarget(event);
155
+ if (!target) return;
156
+ if (replaying === target.anchor) {
157
+ replaying = null;
158
+ return;
159
+ }
160
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
161
+ return;
162
+ }
163
+ const pending = pressed && pressed.key === target.key ? pressed : requestDecision(target.key);
164
+ pressed = null;
165
+ const record = (value) => {
166
+ window.__preprDecision = { key: target.key, value };
167
+ };
168
+ if (pending.result !== void 0) {
169
+ record(pending.result);
170
+ return;
171
+ }
172
+ event.preventDefault();
173
+ event.stopImmediatePropagation();
174
+ void pending.value.then((value) => {
175
+ record(value);
176
+ replaying = target.anchor;
177
+ target.anchor.click();
287
178
  });
179
+ }
180
+ var listeners = 0;
181
+ function useDecisionRequests() {
288
182
  useEffect2(() => {
289
- if (!refresh) return;
290
- const started = createPreprRefreshScheduler({ freshFor });
291
- setScheduler(started);
183
+ if (listeners++ === 0) {
184
+ window.addEventListener("pointerdown", onPointerDown, true);
185
+ window.addEventListener("click", onClick, true);
186
+ window.addEventListener("popstate", onPopState);
187
+ }
292
188
  return () => {
293
- started.stop();
294
- setScheduler(null);
189
+ if (--listeners === 0) {
190
+ window.removeEventListener("pointerdown", onPointerDown, true);
191
+ window.removeEventListener("click", onClick, true);
192
+ window.removeEventListener("popstate", onPopState);
193
+ }
295
194
  };
296
- }, [refresh, freshFor]);
297
- return scheduler ? createElement(
298
- Suspense,
299
- { fallback: null },
300
- createElement(ViewCounter, { scheduler })
301
- ) : null;
195
+ }, []);
302
196
  }
303
- function ViewCounter({
304
- scheduler
305
- }) {
306
- const pathname = usePathname();
307
- useEffect2(() => {
308
- scheduler.view();
309
- }, [pathname, scheduler]);
197
+ function PreprPersonalizationSwitch() {
198
+ useDecisionRequests();
199
+ return null;
200
+ }
201
+ function PreprDecisionApplier() {
202
+ useDecisionRequests();
203
+ useLayoutEffect(() => {
204
+ const key = location.pathname + location.search;
205
+ const remember = () => {
206
+ const state = window.__preprDecision;
207
+ if (state) shown.set(key, state.value);
208
+ };
209
+ const previous = historyStep ? shown.get(key) : void 0;
210
+ historyStep = false;
211
+ if (applyPreprDecisions(previous)) {
212
+ remember();
213
+ return;
214
+ }
215
+ let live = true;
216
+ const started = window.__preprPending;
217
+ const value = started?.key === key ? started.value : requestDecision(key).value;
218
+ void value.then((value2) => {
219
+ if (live && location.pathname + location.search === key) {
220
+ applyPreprDecisions(value2);
221
+ remember();
222
+ }
223
+ });
224
+ return () => {
225
+ live = false;
226
+ };
227
+ });
310
228
  return null;
311
229
  }
312
230
  var VARIANT_KEY_ATTRIBUTE = "data-prepr-variant-key";
313
231
  function PreprVariantTracking({
314
232
  variantKeys,
315
- ab,
316
233
  children
317
234
  }) {
318
235
  const ref = useRef(null);
319
236
  const keys = variantKeys.join(" ");
320
237
  useLayoutEffect(() => {
321
238
  let tagged = [];
322
- const timer = setTimeout(() => {
239
+ const wrapper = ref.current?.closest("[data-prepr-v]");
240
+ const shown2 = () => !wrapper || wrapper.hasAttribute("data-prepr-show");
241
+ const untag = () => {
242
+ for (const [element] of tagged)
243
+ element.removeAttribute(VARIANT_KEY_ATTRIBUTE);
244
+ tagged = [];
245
+ };
246
+ const tag = () => {
247
+ if (!shown2()) return untag();
248
+ if (tagged.length > 0) return;
323
249
  const elements = [...ref.current?.children ?? []];
324
250
  const list = keys.split(" ");
325
251
  const pairs = elements.length === list.length ? elements.map((element, i) => [element, list[i]]) : elements.slice(0, 1).map((element) => [element, list[0]]);
@@ -328,22 +254,27 @@ function PreprVariantTracking({
328
254
  element.setAttribute(VARIANT_KEY_ATTRIBUTE, key);
329
255
  return true;
330
256
  });
257
+ };
258
+ const timer = setTimeout(tag);
259
+ const observer = wrapper && new MutationObserver(tag);
260
+ observer?.observe(wrapper, {
261
+ attributes: true,
262
+ attributeFilter: ["data-prepr-show"]
331
263
  });
332
264
  return () => {
333
265
  clearTimeout(timer);
334
- for (const [element] of tagged) {
335
- element.removeAttribute(VARIANT_KEY_ATTRIBUTE);
336
- }
266
+ observer?.disconnect();
267
+ untag();
337
268
  };
338
269
  }, [keys]);
339
270
  return createElement(
340
271
  "div",
341
- ab === void 0 ? { ref, style: { display: "contents" } } : { ref, "data-prepr-ab": ab },
272
+ { ref, style: { display: "contents" } },
342
273
  children
343
274
  );
344
275
  }
345
276
  export {
346
- PreprAbSwitch,
277
+ PreprDecisionApplier,
347
278
  PreprPersonalizationSwitch,
348
279
  PreprToolbar,
349
280
  PreprTrackingPixel,