@sentientui/react 0.4.0 → 0.6.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.
- package/README.md +15 -1
- package/dist/index.d.cts +21 -4
- package/dist/index.d.ts +21 -4
- package/dist/index.js +1 -514
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -478
- package/dist/index.mjs.map +1 -1
- package/dist/next/adaptive-root-client.js +1 -82
- package/dist/next/adaptive-root-client.js.map +1 -1
- package/dist/next/adaptive-root.d.ts +2 -1
- package/dist/next/adaptive-root.js +1 -152
- package/dist/next/adaptive-root.js.map +1 -1
- package/dist/server.d.cts +4 -4
- package/dist/server.d.ts +4 -4
- package/dist/server.js +1 -87
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +1 -51
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,480 +1,3 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
|
|
3
|
-
// src/provider.tsx
|
|
4
|
-
import { createContext, useContext, useEffect, useState } from "react";
|
|
5
|
-
import { init } from "@sentientui/core";
|
|
6
|
-
import { jsx } from "react/jsx-runtime";
|
|
7
|
-
var AdaptiveContext = createContext({
|
|
8
|
-
client: null,
|
|
9
|
-
apiKey: "",
|
|
10
|
-
initialAssignments: {},
|
|
11
|
-
sessionSegment: "desktop:direct",
|
|
12
|
-
ssrFallback: "first",
|
|
13
|
-
onAssignment: void 0,
|
|
14
|
-
initialLayoutOrder: null
|
|
15
|
-
});
|
|
16
|
-
function AdaptiveProvider(props) {
|
|
17
|
-
var _a, _b, _c, _d;
|
|
18
|
-
const [client, setClient] = useState(null);
|
|
19
|
-
useEffect(() => {
|
|
20
|
-
if (props.consent === false && !props.preConsentBehavior) {
|
|
21
|
-
setClient((prev) => {
|
|
22
|
-
prev == null ? void 0 : prev.destroy();
|
|
23
|
-
return null;
|
|
24
|
-
});
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
const c = init({
|
|
28
|
-
apiKey: props.apiKey,
|
|
29
|
-
context: props.context,
|
|
30
|
-
debug: props.debug,
|
|
31
|
-
initialAssignments: props.initialAssignments,
|
|
32
|
-
sessionSegment: props.sessionSegment,
|
|
33
|
-
consent: props.consent,
|
|
34
|
-
preConsentBehavior: props.preConsentBehavior
|
|
35
|
-
});
|
|
36
|
-
setClient(c);
|
|
37
|
-
return () => {
|
|
38
|
-
c.destroy();
|
|
39
|
-
};
|
|
40
|
-
}, [props.consent]);
|
|
41
|
-
const ssrFallback = (_a = props.ssrFallback) != null ? _a : "first";
|
|
42
|
-
return /* @__PURE__ */ jsx(
|
|
43
|
-
AdaptiveContext.Provider,
|
|
44
|
-
{
|
|
45
|
-
value: {
|
|
46
|
-
client,
|
|
47
|
-
apiKey: props.apiKey,
|
|
48
|
-
initialAssignments: (_b = props.initialAssignments) != null ? _b : {},
|
|
49
|
-
sessionSegment: (_c = props.sessionSegment) != null ? _c : "desktop:direct",
|
|
50
|
-
ssrFallback,
|
|
51
|
-
onAssignment: props.onAssignment,
|
|
52
|
-
initialLayoutOrder: (_d = props.initialLayoutOrder) != null ? _d : null
|
|
53
|
-
},
|
|
54
|
-
children: props.children
|
|
55
|
-
}
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
function useSentient() {
|
|
59
|
-
return useContext(AdaptiveContext).client;
|
|
60
|
-
}
|
|
61
|
-
function useAdaptiveApiKey() {
|
|
62
|
-
return useContext(AdaptiveContext).apiKey;
|
|
63
|
-
}
|
|
64
|
-
function useInitialAssignments() {
|
|
65
|
-
return useContext(AdaptiveContext).initialAssignments;
|
|
66
|
-
}
|
|
67
|
-
function useSessionSegment() {
|
|
68
|
-
return useContext(AdaptiveContext).sessionSegment;
|
|
69
|
-
}
|
|
70
|
-
function useSsrFallback() {
|
|
71
|
-
return useContext(AdaptiveContext).ssrFallback;
|
|
72
|
-
}
|
|
73
|
-
function useOnAssignment() {
|
|
74
|
-
return useContext(AdaptiveContext).onAssignment;
|
|
75
|
-
}
|
|
76
|
-
function useLayoutOrder() {
|
|
77
|
-
return useContext(AdaptiveContext).initialLayoutOrder;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// src/adaptive.tsx
|
|
81
|
-
import { memo, useEffect as useEffect3, useMemo, useRef as useRef2, useState as useState3 } from "react";
|
|
82
|
-
|
|
83
|
-
// src/use-assignment.ts
|
|
84
|
-
import { useEffect as useEffect2, useRef, useState as useState2 } from "react";
|
|
85
|
-
|
|
86
|
-
// src/weights-store.ts
|
|
87
|
-
var store = /* @__PURE__ */ new Map();
|
|
88
|
-
var listeners = /* @__PURE__ */ new Map();
|
|
89
|
-
function subscribe(componentId, cb) {
|
|
90
|
-
let set = listeners.get(componentId);
|
|
91
|
-
if (!set) {
|
|
92
|
-
set = /* @__PURE__ */ new Set();
|
|
93
|
-
listeners.set(componentId, set);
|
|
94
|
-
}
|
|
95
|
-
set.add(cb);
|
|
96
|
-
return () => {
|
|
97
|
-
set.delete(cb);
|
|
98
|
-
if (set.size === 0) listeners.delete(componentId);
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
function update(componentId, weights) {
|
|
102
|
-
store.set(componentId, weights);
|
|
103
|
-
const set = listeners.get(componentId);
|
|
104
|
-
if (!set) return;
|
|
105
|
-
for (const cb of set) {
|
|
106
|
-
try {
|
|
107
|
-
cb(weights);
|
|
108
|
-
} catch (e) {
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
function getWeights(componentId) {
|
|
113
|
-
var _a;
|
|
114
|
-
return (_a = store.get(componentId)) != null ? _a : null;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// src/use-assignment.ts
|
|
118
|
-
function getDevOverride(componentId) {
|
|
119
|
-
var _a;
|
|
120
|
-
if (typeof window === "undefined") return null;
|
|
121
|
-
const global = (_a = window.__sentient_overrides) == null ? void 0 : _a[componentId];
|
|
122
|
-
if (global) return global;
|
|
123
|
-
try {
|
|
124
|
-
const params = new URLSearchParams(window.location.search);
|
|
125
|
-
for (const raw of params.getAll("sentient_variant")) {
|
|
126
|
-
const sep = raw.indexOf(":");
|
|
127
|
-
if (sep === -1) continue;
|
|
128
|
-
if (raw.slice(0, sep) === componentId) return raw.slice(sep + 1);
|
|
129
|
-
}
|
|
130
|
-
} catch (e) {
|
|
131
|
-
}
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
134
|
-
function pickFromWeights(weights, variantIds) {
|
|
135
|
-
var _a;
|
|
136
|
-
let best = null;
|
|
137
|
-
for (const v of weights.variants) {
|
|
138
|
-
if (!variantIds.includes(v.variantId)) continue;
|
|
139
|
-
if (!best || v.avgReward > best.avgReward) {
|
|
140
|
-
best = { variantId: v.variantId, avgReward: v.avgReward };
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return (_a = best == null ? void 0 : best.variantId) != null ? _a : null;
|
|
144
|
-
}
|
|
145
|
-
function useAssignment(componentId, variantIds, agentData) {
|
|
146
|
-
const initialAssignments = useInitialAssignments();
|
|
147
|
-
const ssrFallback = useSsrFallback();
|
|
148
|
-
const client = useSentient();
|
|
149
|
-
const segment = useSessionSegment();
|
|
150
|
-
const onAssignment = useOnAssignment();
|
|
151
|
-
const assignmentReportedRef = useRef(null);
|
|
152
|
-
const devOverride = getDevOverride(componentId);
|
|
153
|
-
const overrideVariant = devOverride && variantIds.includes(devOverride) ? devOverride : null;
|
|
154
|
-
if (overrideVariant) {
|
|
155
|
-
console.info(`[sentient] override active: ${componentId} -> ${overrideVariant}`);
|
|
156
|
-
}
|
|
157
|
-
const initial = (() => {
|
|
158
|
-
var _a, _b;
|
|
159
|
-
if (overrideVariant) {
|
|
160
|
-
return { variantId: overrideVariant, content: null, isLoading: false };
|
|
161
|
-
}
|
|
162
|
-
if (!client) {
|
|
163
|
-
const preloaded = initialAssignments[componentId];
|
|
164
|
-
if (preloaded && variantIds.includes(preloaded)) {
|
|
165
|
-
return { variantId: preloaded, content: null, isLoading: false };
|
|
166
|
-
}
|
|
167
|
-
if (ssrFallback === "first" && variantIds.length > 0) {
|
|
168
|
-
return { variantId: variantIds[0], content: null, isLoading: false };
|
|
169
|
-
}
|
|
170
|
-
return { variantId: null, content: null, isLoading: true };
|
|
171
|
-
}
|
|
172
|
-
const cached = client.getAssignment(componentId, segment);
|
|
173
|
-
if (cached && (variantIds.includes(cached.variantId) || cached.content)) {
|
|
174
|
-
return { variantId: cached.variantId, content: (_a = cached.content) != null ? _a : null, isLoading: false };
|
|
175
|
-
}
|
|
176
|
-
const weights = getWeights(componentId);
|
|
177
|
-
if (weights) {
|
|
178
|
-
const chosen = pickFromWeights(weights, variantIds);
|
|
179
|
-
if (chosen) return { variantId: chosen, content: null, isLoading: false };
|
|
180
|
-
}
|
|
181
|
-
return { variantId: (_b = variantIds[0]) != null ? _b : null, content: null, isLoading: false };
|
|
182
|
-
})();
|
|
183
|
-
const [state, setState] = useState2(initial);
|
|
184
|
-
const reportAssignment = (variantId) => {
|
|
185
|
-
if (!onAssignment) return;
|
|
186
|
-
if (assignmentReportedRef.current === variantId) return;
|
|
187
|
-
assignmentReportedRef.current = variantId;
|
|
188
|
-
onAssignment(componentId, variantId);
|
|
189
|
-
};
|
|
190
|
-
useEffect2(() => {
|
|
191
|
-
var _a;
|
|
192
|
-
if (overrideVariant) return;
|
|
193
|
-
if (!client) return;
|
|
194
|
-
const cached = client.getAssignment(componentId, segment);
|
|
195
|
-
if (cached && (variantIds.includes(cached.variantId) || cached.content)) {
|
|
196
|
-
setState({ variantId: cached.variantId, content: (_a = cached.content) != null ? _a : null, isLoading: false });
|
|
197
|
-
reportAssignment(cached.variantId);
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
setState((prev) => {
|
|
201
|
-
var _a2;
|
|
202
|
-
return prev.variantId ? prev : { variantId: (_a2 = variantIds[0]) != null ? _a2 : null, content: null, isLoading: false };
|
|
203
|
-
});
|
|
204
|
-
}, [overrideVariant, client, componentId, segment]);
|
|
205
|
-
useEffect2(() => {
|
|
206
|
-
if (overrideVariant) return;
|
|
207
|
-
if (!client) return;
|
|
208
|
-
const cached = client.getAssignment(componentId, segment);
|
|
209
|
-
if (cached && variantIds.includes(cached.variantId)) return;
|
|
210
|
-
let cancelled = false;
|
|
211
|
-
void client.assign(componentId, variantIds, agentData).then((result) => {
|
|
212
|
-
var _a;
|
|
213
|
-
if (cancelled) return;
|
|
214
|
-
if (!result) return;
|
|
215
|
-
if (!variantIds.includes(result.variantId) && !result.content) return;
|
|
216
|
-
setState({ variantId: result.variantId, content: (_a = result.content) != null ? _a : null, isLoading: false });
|
|
217
|
-
reportAssignment(result.variantId);
|
|
218
|
-
});
|
|
219
|
-
return () => {
|
|
220
|
-
cancelled = true;
|
|
221
|
-
};
|
|
222
|
-
}, [overrideVariant, client, componentId, segment]);
|
|
223
|
-
useEffect2(() => {
|
|
224
|
-
if (overrideVariant) return;
|
|
225
|
-
if (!client) return;
|
|
226
|
-
return subscribe(componentId, (weights) => {
|
|
227
|
-
var _a;
|
|
228
|
-
const cached = client.getAssignment(componentId, segment);
|
|
229
|
-
if (cached && (variantIds.includes(cached.variantId) || cached.content)) {
|
|
230
|
-
setState({ variantId: cached.variantId, content: (_a = cached.content) != null ? _a : null, isLoading: false });
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
const chosen = pickFromWeights(weights, variantIds);
|
|
234
|
-
if (chosen) setState({ variantId: chosen, content: null, isLoading: false });
|
|
235
|
-
});
|
|
236
|
-
}, [overrideVariant, client, componentId, segment]);
|
|
237
|
-
if (overrideVariant) {
|
|
238
|
-
return { variantId: overrideVariant, content: null, isLoading: false };
|
|
239
|
-
}
|
|
240
|
-
return state;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// src/adaptive.tsx
|
|
244
|
-
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
245
|
-
function normalize(goal) {
|
|
246
|
-
if (typeof goal === "string") return { type: "click" };
|
|
247
|
-
return goal;
|
|
248
|
-
}
|
|
249
|
-
function isClickableTarget(el) {
|
|
250
|
-
if (!(el instanceof Element)) return false;
|
|
251
|
-
const tag = el.tagName.toLowerCase();
|
|
252
|
-
if (tag === "a" || tag === "button") return true;
|
|
253
|
-
const role = el.getAttribute("role");
|
|
254
|
-
return role === "button";
|
|
255
|
-
}
|
|
256
|
-
function findClickable(start, container) {
|
|
257
|
-
let cursor = start;
|
|
258
|
-
while (cursor && cursor !== container) {
|
|
259
|
-
if (isClickableTarget(cursor)) return true;
|
|
260
|
-
cursor = cursor.parentElement;
|
|
261
|
-
}
|
|
262
|
-
return false;
|
|
263
|
-
}
|
|
264
|
-
function AdaptiveImpl(props) {
|
|
265
|
-
var _a, _b;
|
|
266
|
-
const client = useSentient();
|
|
267
|
-
const apiKey = useAdaptiveApiKey();
|
|
268
|
-
const variantIds = useMemo(() => Object.keys(props.variants), [props.variants]);
|
|
269
|
-
const { variantId, content } = useAssignment(props.id, variantIds, props.agentData);
|
|
270
|
-
const containerRef = useRef2(null);
|
|
271
|
-
const [mounted, setMounted] = useState3(false);
|
|
272
|
-
useEffect3(() => {
|
|
273
|
-
setMounted(true);
|
|
274
|
-
}, []);
|
|
275
|
-
const goalFiredRef = useRef2(false);
|
|
276
|
-
const assignTrackedRef = useRef2(null);
|
|
277
|
-
const goal = useMemo(() => normalize(props.goal), [props.goal]);
|
|
278
|
-
const goalLabel = typeof props.goal === "string" ? props.goal : goal.type;
|
|
279
|
-
useEffect3(() => {
|
|
280
|
-
var _a2, _b2;
|
|
281
|
-
if (!client || !variantId || !apiKey) return;
|
|
282
|
-
if (assignTrackedRef.current === variantId) return;
|
|
283
|
-
const rawHtml = (_b2 = (_a2 = containerRef.current) == null ? void 0 : _a2.innerHTML) != null ? _b2 : "";
|
|
284
|
-
if (!rawHtml && !mounted) return;
|
|
285
|
-
assignTrackedRef.current = variantId;
|
|
286
|
-
client.track({
|
|
287
|
-
projectId: apiKey,
|
|
288
|
-
componentId: props.id,
|
|
289
|
-
variantId,
|
|
290
|
-
eventType: "variant_assigned",
|
|
291
|
-
payload: rawHtml ? { previewHtml: rawHtml.slice(0, 3e4) } : {}
|
|
292
|
-
});
|
|
293
|
-
}, [client, variantId, apiKey, props.id, mounted]);
|
|
294
|
-
useEffect3(() => {
|
|
295
|
-
goalFiredRef.current = false;
|
|
296
|
-
}, [variantId]);
|
|
297
|
-
useEffect3(() => {
|
|
298
|
-
if (!client || !variantId) return;
|
|
299
|
-
const node = containerRef.current;
|
|
300
|
-
if (!node) return;
|
|
301
|
-
let timerId = null;
|
|
302
|
-
let hoverStart = 0;
|
|
303
|
-
const onEnter = () => {
|
|
304
|
-
hoverStart = Date.now();
|
|
305
|
-
timerId = setTimeout(() => {
|
|
306
|
-
client.track({
|
|
307
|
-
projectId: apiKey,
|
|
308
|
-
componentId: props.id,
|
|
309
|
-
variantId,
|
|
310
|
-
eventType: "cursor_signal",
|
|
311
|
-
payload: { hoverDuration: Date.now() - hoverStart }
|
|
312
|
-
});
|
|
313
|
-
timerId = null;
|
|
314
|
-
}, 800);
|
|
315
|
-
};
|
|
316
|
-
const onLeave = () => {
|
|
317
|
-
if (timerId !== null) {
|
|
318
|
-
clearTimeout(timerId);
|
|
319
|
-
timerId = null;
|
|
320
|
-
}
|
|
321
|
-
};
|
|
322
|
-
node.addEventListener("mouseenter", onEnter);
|
|
323
|
-
node.addEventListener("mouseleave", onLeave);
|
|
324
|
-
return () => {
|
|
325
|
-
node.removeEventListener("mouseenter", onEnter);
|
|
326
|
-
node.removeEventListener("mouseleave", onLeave);
|
|
327
|
-
if (timerId !== null) clearTimeout(timerId);
|
|
328
|
-
};
|
|
329
|
-
}, [client, variantId, apiKey, props.id]);
|
|
330
|
-
useEffect3(() => {
|
|
331
|
-
if (!client || !variantId) return;
|
|
332
|
-
const node = containerRef.current;
|
|
333
|
-
if (!node) return;
|
|
334
|
-
const fireGoal = () => {
|
|
335
|
-
if (goalFiredRef.current) return;
|
|
336
|
-
goalFiredRef.current = true;
|
|
337
|
-
client.track({
|
|
338
|
-
projectId: apiKey,
|
|
339
|
-
componentId: props.id,
|
|
340
|
-
variantId,
|
|
341
|
-
eventType: "goal_achieved",
|
|
342
|
-
goalType: goalLabel,
|
|
343
|
-
payload: { reward: 1 }
|
|
344
|
-
});
|
|
345
|
-
};
|
|
346
|
-
const subgoals = goal.type === "composite" ? goal.all : [goal];
|
|
347
|
-
const remaining = new Set(subgoals.map((_, i) => i));
|
|
348
|
-
const checkComposite = (idx) => {
|
|
349
|
-
remaining.delete(idx);
|
|
350
|
-
if (remaining.size === 0) fireGoal();
|
|
351
|
-
};
|
|
352
|
-
const cleanups = [];
|
|
353
|
-
subgoals.forEach((sub, idx) => {
|
|
354
|
-
if (sub.type === "click") {
|
|
355
|
-
const onClick = (e) => {
|
|
356
|
-
const target = e.target;
|
|
357
|
-
if (!(target instanceof Element)) return;
|
|
358
|
-
if (!findClickable(target, node)) return;
|
|
359
|
-
if (goal.type === "composite") checkComposite(idx);
|
|
360
|
-
else fireGoal();
|
|
361
|
-
};
|
|
362
|
-
node.addEventListener("click", onClick);
|
|
363
|
-
cleanups.push(() => node.removeEventListener("click", onClick));
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
if (sub.type === "form_submit") {
|
|
367
|
-
const onSubmit = (e) => {
|
|
368
|
-
if (!(e.target instanceof HTMLFormElement)) return;
|
|
369
|
-
if (!node.contains(e.target)) return;
|
|
370
|
-
if (goal.type === "composite") checkComposite(idx);
|
|
371
|
-
else fireGoal();
|
|
372
|
-
};
|
|
373
|
-
node.addEventListener("submit", onSubmit);
|
|
374
|
-
cleanups.push(() => node.removeEventListener("submit", onSubmit));
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
if (sub.type === "scroll_depth") {
|
|
378
|
-
const threshold = Math.max(0, Math.min(1, sub.threshold));
|
|
379
|
-
const io = new IntersectionObserver(
|
|
380
|
-
(entries) => {
|
|
381
|
-
for (const entry of entries) {
|
|
382
|
-
if (entry.intersectionRatio >= threshold) {
|
|
383
|
-
if (goal.type === "composite") checkComposite(idx);
|
|
384
|
-
else fireGoal();
|
|
385
|
-
io.disconnect();
|
|
386
|
-
break;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
},
|
|
390
|
-
{ threshold: [threshold] }
|
|
391
|
-
);
|
|
392
|
-
io.observe(node);
|
|
393
|
-
cleanups.push(() => io.disconnect());
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
});
|
|
397
|
-
return () => {
|
|
398
|
-
for (const c of cleanups) c();
|
|
399
|
-
};
|
|
400
|
-
}, [client, variantId, apiKey, props.id, goal, goalLabel]);
|
|
401
|
-
if (props.clientOnly && (!mounted || !client)) return null;
|
|
402
|
-
if (!variantId) return null;
|
|
403
|
-
const jsxContent = (_a = props.variants[variantId]) != null ? _a : null;
|
|
404
|
-
const managedContent = jsxContent === null ? content : null;
|
|
405
|
-
if (((_b = process == null ? void 0 : process.env) == null ? void 0 : _b.NODE_ENV) !== "production" && jsxContent === null && managedContent === null) {
|
|
406
|
-
console.warn(
|
|
407
|
-
`[sentient] <Adaptive id="${props.id}"> was assigned variant "${variantId}" but no matching key exists in props.variants. If this is a dashboard-managed text variant, use <AdaptiveText id="${props.id}"> instead.`
|
|
408
|
-
);
|
|
409
|
-
}
|
|
410
|
-
return /* @__PURE__ */ jsx2("div", { ref: containerRef, "data-sentient-id": props.id, "data-sentient-variant": variantId, children: jsxContent != null ? jsxContent : managedContent });
|
|
411
|
-
}
|
|
412
|
-
var Adaptive = memo(AdaptiveImpl, (prev, next) => {
|
|
413
|
-
if (prev.id !== next.id) return false;
|
|
414
|
-
if (prev.goal !== next.goal) return false;
|
|
415
|
-
if (prev.variants === next.variants) return true;
|
|
416
|
-
const prevKeys = Object.keys(prev.variants);
|
|
417
|
-
const nextKeys = Object.keys(next.variants);
|
|
418
|
-
if (prevKeys.length !== nextKeys.length) return false;
|
|
419
|
-
return prevKeys.every((k) => k in next.variants);
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
// src/adaptive-text.tsx
|
|
423
|
-
import { useEffect as useEffect4, useRef as useRef3, useState as useState4 } from "react";
|
|
424
|
-
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
425
|
-
function AdaptiveText({
|
|
426
|
-
id,
|
|
427
|
-
default: defaultText,
|
|
428
|
-
component: Tag = "span",
|
|
429
|
-
className
|
|
430
|
-
}) {
|
|
431
|
-
const client = useSentient();
|
|
432
|
-
const apiKey = useAdaptiveApiKey();
|
|
433
|
-
const onAssignment = useOnAssignment();
|
|
434
|
-
const [text, setText] = useState4(null);
|
|
435
|
-
const [variantId, setVariantId] = useState4(null);
|
|
436
|
-
const trackedRef = useRef3(null);
|
|
437
|
-
useEffect4(() => {
|
|
438
|
-
if (!client) return;
|
|
439
|
-
let cancelled = false;
|
|
440
|
-
void client.assign(id).then((result) => {
|
|
441
|
-
if (cancelled || !result) return;
|
|
442
|
-
setVariantId(result.variantId);
|
|
443
|
-
if (result.content) setText(result.content);
|
|
444
|
-
});
|
|
445
|
-
return () => {
|
|
446
|
-
cancelled = true;
|
|
447
|
-
};
|
|
448
|
-
}, [client, id]);
|
|
449
|
-
useEffect4(() => {
|
|
450
|
-
if (!client || !variantId || !apiKey) return;
|
|
451
|
-
if (trackedRef.current === variantId) return;
|
|
452
|
-
trackedRef.current = variantId;
|
|
453
|
-
client.track({
|
|
454
|
-
projectId: apiKey,
|
|
455
|
-
componentId: id,
|
|
456
|
-
variantId,
|
|
457
|
-
eventType: "variant_assigned",
|
|
458
|
-
payload: {}
|
|
459
|
-
});
|
|
460
|
-
onAssignment == null ? void 0 : onAssignment(id, variantId);
|
|
461
|
-
}, [client, variantId, apiKey, id, onAssignment]);
|
|
462
|
-
return /* @__PURE__ */ jsx3(Tag, { className, children: text != null ? text : defaultText });
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// src/segment.ts
|
|
466
|
-
import { deriveSessionSegment } from "@sentientui/core";
|
|
467
|
-
export {
|
|
468
|
-
Adaptive,
|
|
469
|
-
AdaptiveProvider,
|
|
470
|
-
AdaptiveText,
|
|
471
|
-
deriveSessionSegment as detectSegment,
|
|
472
|
-
getWeights,
|
|
473
|
-
subscribe as subscribeWeights,
|
|
474
|
-
update as updateWeights,
|
|
475
|
-
useAssignment,
|
|
476
|
-
useInitialAssignments,
|
|
477
|
-
useLayoutOrder,
|
|
478
|
-
useSentient
|
|
479
|
-
};
|
|
2
|
+
var se=Object.defineProperty;var X=Object.getOwnPropertySymbols;var oe=Object.prototype.hasOwnProperty,ae=Object.prototype.propertyIsEnumerable;var z=(e,t,n)=>t in e?se(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,J=(e,t)=>{for(var n in t||(t={}))oe.call(t,n)&&z(e,n,t[n]);if(X)for(var n of X(t))ae.call(t,n)&&z(e,n,t[n]);return e};import{createContext as le,useContext as I,useEffect as ce,useMemo as ue,useState as U}from"react";import{detectDeviceClass as de,detectTrafficSource as ge,init as fe}from"@sentientui/core";import{jsx as ye}from"react/jsx-runtime";function me(){var e,t;if(typeof window=="undefined")return"desktop:direct";try{let n=de((e=navigator.userAgent)!=null?e:""),d=ge((t=document.referrer)!=null?t:"",window.location.origin);return`${n}:${d}`}catch(n){return"desktop:direct"}}var C=le({client:null,apiKey:"",initialAssignments:{},sessionSegment:"desktop:direct",ssrFallback:"first",onAssignment:void 0,initialLayoutOrder:null});function ve(e){var o;let[t,n]=U(null),[d]=U(()=>{var l;return(l=e.sessionSegment)!=null?l:me()});ce(()=>{if(e.consent===!1&&!e.preConsentBehavior){n(v=>(v==null||v.destroy(),null));return}let l=fe({apiKey:e.apiKey,context:e.context,debug:e.debug,initialAssignments:e.initialAssignments,sessionSegment:d,consent:e.consent,preConsentBehavior:e.preConsentBehavior});return n(l),()=>{l.destroy()}},[e.consent]);let i=(o=e.ssrFallback)!=null?o:"first",m=ue(()=>{var l,v;return{client:t,apiKey:e.apiKey,initialAssignments:(l=e.initialAssignments)!=null?l:{},sessionSegment:d,ssrFallback:i,onAssignment:e.onAssignment,initialLayoutOrder:(v=e.initialLayoutOrder)!=null?v:null}},[t,e.apiKey,e.initialAssignments,d,i,e.onAssignment,e.initialLayoutOrder]);return ye(C.Provider,{value:m,children:e.children})}function R(){return I(C).client}function F(){return I(C).apiKey}function V(){return I(C).initialAssignments}function q(){return I(C).sessionSegment}function Q(){return I(C).ssrFallback}function D(){return I(C).onAssignment}function pe(){return I(C).initialLayoutOrder}import{memo as ke,useEffect as T,useMemo as ee,useRef as B,useState as xe}from"react";import{attachMicroSignalDetectors as we}from"@sentientui/core";import{useEffect as H,useRef as Ae,useState as Se}from"react";var Y=new Map,M=new Map;function N(e,t){let n=M.get(e);return n||(n=new Set,M.set(e,n)),n.add(t),()=>{n.delete(t),n.size===0&&M.delete(e)}}function he(e,t){Y.set(e,t);let n=M.get(e);if(n)for(let d of n)try{d(t)}catch(i){}}function j(e){var t;return(t=Y.get(e))!=null?t:null}function be(e){var n;if(typeof window=="undefined")return null;let t=(n=window.__sentient_overrides)==null?void 0:n[e];if(t)return t;try{let d=new URLSearchParams(window.location.search);for(let i of d.getAll("sentient_variant")){let m=i.indexOf(":");if(m!==-1&&i.slice(0,m)===e)return i.slice(m+1)}}catch(d){}return null}function Z(e,t){var d;let n=null;for(let i of e.variants)t.includes(i.variantId)&&(!n||i.avgReward>n.avgReward)&&(n={variantId:i.variantId,avgReward:i.avgReward});return(d=n==null?void 0:n.variantId)!=null?d:null}function $(e,t,n,d){let i=V(),m=Q(),o=R(),l=q(),v=D(),p=Ae(null),b=be(e),a=b&&t.includes(b)?b:null;a&&console.info(`[sentient] override active: ${e} -> ${a}`);let E=(()=>{var s,u;if(a)return{variantId:a,content:null,isLoading:!1};if(!o){let g=i[e];return g&&t.includes(g)?{variantId:g,content:null,isLoading:!1}:m==="first"&&t.length>0?{variantId:t[0],content:null,isLoading:!1}:{variantId:null,content:null,isLoading:!0}}let c=o.getAssignment(e,l);if(c&&(t.includes(c.variantId)||c.content))return{variantId:c.variantId,content:(s=c.content)!=null?s:null,isLoading:!1};let r=j(e);if(r){let g=Z(r,t);if(g)return{variantId:g,content:null,isLoading:!1}}return{variantId:(u=t[0])!=null?u:null,content:null,isLoading:!1}})(),[y,k]=Se(E),O=c=>{v&&p.current!==c&&(p.current=c,v(e,c))};return H(()=>{var r;if(a||!o)return;let c=o.getAssignment(e,l);if(c&&(t.includes(c.variantId)||c.content)){k({variantId:c.variantId,content:(r=c.content)!=null?r:null,isLoading:!1}),O(c.variantId);return}k(s=>{var u;return s.variantId?s:{variantId:(u=t[0])!=null?u:null,content:null,isLoading:!1}})},[a,o,e,l]),H(()=>{if(a||!o)return;let c=o.getAssignment(e,l);if(c&&t.includes(c.variantId))return;let r=!1;return o.assign(e,t,n,d).then(s=>{var u;r||s&&(!t.includes(s.variantId)&&!s.content||(k({variantId:s.variantId,content:(u=s.content)!=null?u:null,isLoading:!1}),O(s.variantId)))}),()=>{r=!0}},[a,o,e,l]),H(()=>{if(!a&&o)return N(e,c=>{var u;let r=o.getAssignment(e,l);if(r&&(t.includes(r.variantId)||r.content)){k({variantId:r.variantId,content:(u=r.content)!=null?u:null,isLoading:!1});return}let s=Z(c,t);s&&k({variantId:s,content:null,isLoading:!1})})},[a,o,e,l]),a?{variantId:a,content:null,isLoading:!1}:y}import{jsx as Te}from"react/jsx-runtime";function Ce(e){return typeof e=="string"?{type:"click"}:e}var Ee=3e4;function Le(e,t){try{let n=`_snt_pv_${e}_${t}`;return sessionStorage.getItem(n)?!1:(sessionStorage.setItem(n,"1"),!0)}catch(n){return!0}}function Ie(e){if(!(e instanceof Element))return!1;let t=e.tagName.toLowerCase();return t==="a"||t==="button"?!0:e.getAttribute("role")==="button"}function te(e,t){let n=e;for(;n&&n!==t;){if(Ie(n))return!0;n=n.parentElement}return!1}function Re(e){var O,c;let t=R(),n=F(),d=ee(()=>Object.keys(e.variants),[e.variants]),{variantId:i,content:m}=$(e.id,d,e.agentData,e.agentDataByVariant),o=B(null),[l,v]=xe(!1);T(()=>{v(!0)},[]);let p=B(!1),b=B(null),a=ee(()=>Ce(e.goal),[e.goal]),E=typeof e.goal=="string"?e.goal:a.type;if(T(()=>{var u,g;if(!t||!i||!n||b.current===i)return;let r=(g=(u=o.current)==null?void 0:u.innerHTML)!=null?g:"";if(!r&&!l)return;b.current=i;let s=r!==""&&Le(e.id,i);t.track({projectId:n,componentId:e.id,variantId:i,eventType:"variant_assigned",payload:s?{previewHtml:r.slice(0,Ee)}:{}})},[t,i,n,e.id,l]),T(()=>{p.current=!1},[i]),T(()=>{if(!t||!i)return;let r=o.current;if(!r)return;let s=null,u=0,g=()=>{u=Date.now(),s=setTimeout(()=>{t.track({projectId:n,componentId:e.id,variantId:i,eventType:"cursor_signal",payload:{hoverDuration:Date.now()-u}}),s=null},800)},_=()=>{s!==null&&(clearTimeout(s),s=null)};return r.addEventListener("mouseenter",g),r.addEventListener("mouseleave",_),()=>{r.removeEventListener("mouseenter",g),r.removeEventListener("mouseleave",_),s!==null&&clearTimeout(s)}},[t,i,n,e.id]),T(()=>{if(!t||!i)return;let r=o.current;if(!r)return;let s=Date.now();return we((u,g={})=>{t.track({projectId:n,componentId:e.id,variantId:i,eventType:"micro_signal",payload:J({signalType:u},g)})},r,s)},[t,i,n,e.id]),T(()=>{if(!t||!i)return;let r=o.current;if(!r)return;if(a.type==="weighted_composite"){let h=new Set,S=[];return a.steps.forEach(({goal:f,name:A,weight:L},W)=>{let K=()=>{h.has(W)||(h.add(W),t.track({projectId:n,componentId:e.id,variantId:i,eventType:"goal_achieved",goalType:A,payload:{reward:L}}),t.goal(A,{},L,W))};if(f.type==="click"){let x=w=>{let P=w.target;P instanceof Element&&te(P,r)&&K()};r.addEventListener("click",x),S.push(()=>r.removeEventListener("click",x));return}if(f.type==="form_submit"){let x=w=>{w.target instanceof HTMLFormElement&&r.contains(w.target)&&K()};r.addEventListener("submit",x),S.push(()=>r.removeEventListener("submit",x));return}if(f.type==="scroll_depth"){let x=Math.max(0,Math.min(1,f.threshold)),w=new IntersectionObserver(P=>{for(let re of P)if(re.intersectionRatio>=x){K(),w.disconnect();break}},{threshold:[x]});w.observe(r),S.push(()=>w.disconnect())}}),()=>{for(let f of S)f()}}let s=()=>{p.current||(p.current=!0,t.track({projectId:n,componentId:e.id,variantId:i,eventType:"goal_achieved",goalType:E,payload:{reward:1}}))},u=a.type==="composite"?a.all:[a],g=new Set(u.map((h,S)=>S)),_=h=>{g.delete(h),g.size===0&&s()},G=[];return u.forEach((h,S)=>{if(h.type==="click"){let f=A=>{let L=A.target;L instanceof Element&&te(L,r)&&(a.type==="composite"?_(S):s())};r.addEventListener("click",f),G.push(()=>r.removeEventListener("click",f));return}if(h.type==="form_submit"){let f=A=>{A.target instanceof HTMLFormElement&&r.contains(A.target)&&(a.type==="composite"?_(S):s())};r.addEventListener("submit",f),G.push(()=>r.removeEventListener("submit",f));return}if(h.type==="scroll_depth"){let f=Math.max(0,Math.min(1,h.threshold)),A=new IntersectionObserver(L=>{for(let W of L)if(W.intersectionRatio>=f){a.type==="composite"?_(S):s(),A.disconnect();break}},{threshold:[f]});A.observe(r),G.push(()=>A.disconnect());return}}),()=>{for(let h of G)h()}},[t,i,n,e.id,a,E]),e.clientOnly&&(!l||!t)||!i)return null;let y=(O=e.variants[i])!=null?O:null,k=y===null?m:null;return((c=process==null?void 0:process.env)==null?void 0:c.NODE_ENV)!=="production"&&y===null&&k===null&&console.warn(`[sentient] <Adaptive id="${e.id}"> was assigned variant "${i}" but no matching key exists in props.variants. If this is a dashboard-managed text variant, use <AdaptiveText id="${e.id}"> instead.`),Te("div",{ref:o,"data-sentient-id":e.id,"data-sentient-variant":i,children:y!=null?y:k})}var _e=ke(Re,(e,t)=>{if(e.id!==t.id||e.goal!==t.goal)return!1;if(e.variants===t.variants)return!0;let n=Object.keys(e.variants),d=Object.keys(t.variants);return n.length!==d.length?!1:n.every(i=>i in t.variants)});import{useEffect as ne,useRef as Oe,useState as ie}from"react";import{jsx as Ge}from"react/jsx-runtime";function We({id:e,default:t,component:n="span",className:d}){let i=R(),m=F(),o=D(),[l,v]=ie(null),[p,b]=ie(null),a=Oe(null);return ne(()=>{if(!i)return;let E=!1;return i.assign(e).then(y=>{E||!y||(b(y.variantId),y.content&&v(y.content))}),()=>{E=!0}},[i,e]),ne(()=>{!i||!p||!m||a.current!==p&&(a.current=p,i.track({projectId:m,componentId:e,variantId:p,eventType:"variant_assigned",payload:{}}),o==null||o(e,p))},[i,p,m,e,o]),Ge(n,{className:d,children:l!=null?l:t})}import{deriveSessionSegment as Pe}from"@sentientui/core";export{_e as Adaptive,ve as AdaptiveProvider,We as AdaptiveText,Pe as detectSegment,j as getWeights,N as subscribeWeights,he as updateWeights,$ as useAssignment,V as useInitialAssignments,pe as useLayoutOrder,R as useSentient};
|
|
480
3
|
//# sourceMappingURL=index.mjs.map
|