featuredrop 2.6.1 → 2.7.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.
- package/.claude-plugin/plugin.json +21 -0
- package/README.md +77 -11
- package/context7.json +15 -0
- package/dist/index.d.cts +89 -1
- package/dist/index.d.ts +89 -1
- package/dist/preact.cjs +19 -10
- package/dist/preact.cjs.map +1 -1
- package/dist/preact.d.cts +93 -1
- package/dist/preact.d.ts +93 -1
- package/dist/preact.js +19 -10
- package/dist/preact.js.map +1 -1
- package/dist/react-hooks.cjs +472 -0
- package/dist/react-hooks.cjs.map +1 -0
- package/dist/react-hooks.d.cts +540 -0
- package/dist/react-hooks.d.ts +540 -0
- package/dist/react-hooks.js +461 -0
- package/dist/react-hooks.js.map +1 -0
- package/dist/react.cjs +19 -10
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +93 -1
- package/dist/react.d.ts +93 -1
- package/dist/react.js +19 -10
- package/dist/react.js.map +1 -1
- package/dist/tailwind.cjs +148 -0
- package/dist/tailwind.cjs.map +1 -0
- package/dist/tailwind.d.cts +38 -0
- package/dist/tailwind.d.ts +38 -0
- package/dist/tailwind.js +146 -0
- package/dist/tailwind.js.map +1 -0
- package/dist/testing.cjs +19 -10
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +90 -0
- package/dist/testing.d.ts +90 -0
- package/dist/testing.js +19 -10
- package/dist/testing.js.map +1 -1
- package/package.json +32 -1
- package/skills/featuredrop-setup/SKILL.md +124 -0
- package/src/ai/claude-skill.md +109 -0
- package/src/ai/cursorrules.txt +13 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createContext, useContext, useRef, useEffect, useMemo, useState, useCallback } from 'react';
|
|
3
|
+
|
|
4
|
+
// src/react/hooks/use-feature-drop.ts
|
|
5
|
+
var FeatureDropContext = createContext(
|
|
6
|
+
null
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
// src/react/hooks/use-feature-drop.ts
|
|
10
|
+
function useFeatureDrop() {
|
|
11
|
+
const context = useContext(FeatureDropContext);
|
|
12
|
+
if (!context) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
"useFeatureDrop must be used within a <FeatureDropProvider>"
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
return context;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/react/hooks/use-new-feature.ts
|
|
21
|
+
function useNewFeature(sidebarKey) {
|
|
22
|
+
const { isNew, getFeature, dismiss } = useFeatureDrop();
|
|
23
|
+
const feature = getFeature(sidebarKey);
|
|
24
|
+
const isNewValue = isNew(sidebarKey);
|
|
25
|
+
return {
|
|
26
|
+
isNew: isNewValue,
|
|
27
|
+
feature,
|
|
28
|
+
dismiss: () => {
|
|
29
|
+
if (feature) {
|
|
30
|
+
dismiss(feature.id);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/react/hooks/use-new-count.ts
|
|
37
|
+
function useNewCount() {
|
|
38
|
+
const { newCount } = useFeatureDrop();
|
|
39
|
+
return newCount;
|
|
40
|
+
}
|
|
41
|
+
function useTabNotification(options = {}) {
|
|
42
|
+
const {
|
|
43
|
+
enabled = true,
|
|
44
|
+
template = "({count}) {title}",
|
|
45
|
+
flash = false,
|
|
46
|
+
flashInterval = 1500
|
|
47
|
+
} = options;
|
|
48
|
+
const { newCount } = useFeatureDrop();
|
|
49
|
+
const originalTitleRef = useRef("");
|
|
50
|
+
const intervalRef = useRef(null);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (typeof document === "undefined") return;
|
|
53
|
+
if (!originalTitleRef.current) {
|
|
54
|
+
originalTitleRef.current = document.title;
|
|
55
|
+
}
|
|
56
|
+
const originalTitle = originalTitleRef.current;
|
|
57
|
+
if (!enabled || newCount === 0) {
|
|
58
|
+
document.title = originalTitle;
|
|
59
|
+
if (intervalRef.current) {
|
|
60
|
+
clearInterval(intervalRef.current);
|
|
61
|
+
intervalRef.current = null;
|
|
62
|
+
}
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const notificationTitle = template.replace("{count}", String(newCount)).replace("{title}", originalTitle);
|
|
66
|
+
if (flash) {
|
|
67
|
+
let showNotification = true;
|
|
68
|
+
document.title = notificationTitle;
|
|
69
|
+
intervalRef.current = setInterval(() => {
|
|
70
|
+
showNotification = !showNotification;
|
|
71
|
+
document.title = showNotification ? notificationTitle : originalTitle;
|
|
72
|
+
}, flashInterval);
|
|
73
|
+
} else {
|
|
74
|
+
document.title = notificationTitle;
|
|
75
|
+
}
|
|
76
|
+
return () => {
|
|
77
|
+
document.title = originalTitle;
|
|
78
|
+
if (intervalRef.current) {
|
|
79
|
+
clearInterval(intervalRef.current);
|
|
80
|
+
intervalRef.current = null;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}, [enabled, newCount, template, flash, flashInterval]);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/analytics.ts
|
|
87
|
+
function createAdoptionMetrics(events) {
|
|
88
|
+
const getAdoptionRate = (featureId) => {
|
|
89
|
+
const seen = events.filter((event) => event.type === "feature_seen" && event.featureId === featureId).length;
|
|
90
|
+
if (seen === 0) return 0;
|
|
91
|
+
const clicked = events.filter((event) => event.type === "feature_clicked" && event.featureId === featureId).length;
|
|
92
|
+
return clicked / seen;
|
|
93
|
+
};
|
|
94
|
+
const getTourCompletionRate = (tourId) => {
|
|
95
|
+
const started = events.filter((event) => event.type === "tour_started" && event.tourId === tourId).length;
|
|
96
|
+
if (started === 0) return 0;
|
|
97
|
+
const completed = events.filter((event) => event.type === "tour_completed" && event.tourId === tourId).length;
|
|
98
|
+
return completed / started;
|
|
99
|
+
};
|
|
100
|
+
const getChecklistCompletionRate = (checklistId) => {
|
|
101
|
+
const taskCompleted = events.filter(
|
|
102
|
+
(event) => event.type === "checklist_task_completed" && event.metadata?.checklistId === checklistId
|
|
103
|
+
).length;
|
|
104
|
+
if (taskCompleted === 0) return 0;
|
|
105
|
+
const completed = events.filter(
|
|
106
|
+
(event) => event.type === "checklist_completed" && event.metadata?.checklistId === checklistId
|
|
107
|
+
).length;
|
|
108
|
+
return completed / taskCompleted;
|
|
109
|
+
};
|
|
110
|
+
const getFeatureEngagement = (featureId) => ({
|
|
111
|
+
seen: events.filter((event) => event.type === "feature_seen" && event.featureId === featureId).length,
|
|
112
|
+
clicked: events.filter((event) => event.type === "feature_clicked" && event.featureId === featureId).length,
|
|
113
|
+
dismissed: events.filter((event) => event.type === "feature_dismissed" && event.featureId === featureId).length
|
|
114
|
+
});
|
|
115
|
+
const getVariantPerformance = (featureId) => {
|
|
116
|
+
const byVariant = /* @__PURE__ */ new Map();
|
|
117
|
+
for (const event of events) {
|
|
118
|
+
if (event.featureId !== featureId) continue;
|
|
119
|
+
const variant = event.variant ?? "control";
|
|
120
|
+
const bucket = byVariant.get(variant) ?? { seen: 0, clicked: 0 };
|
|
121
|
+
if (event.type === "feature_seen") bucket.seen += 1;
|
|
122
|
+
if (event.type === "feature_clicked") bucket.clicked += 1;
|
|
123
|
+
byVariant.set(variant, bucket);
|
|
124
|
+
}
|
|
125
|
+
const output = {};
|
|
126
|
+
for (const [variant, bucket] of byVariant.entries()) {
|
|
127
|
+
output[variant] = bucket.seen === 0 ? 0 : bucket.clicked / bucket.seen;
|
|
128
|
+
}
|
|
129
|
+
return output;
|
|
130
|
+
};
|
|
131
|
+
return {
|
|
132
|
+
getAdoptionRate,
|
|
133
|
+
getTourCompletionRate,
|
|
134
|
+
getChecklistCompletionRate,
|
|
135
|
+
getFeatureEngagement,
|
|
136
|
+
getVariantPerformance
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/react/hooks/use-adoption-analytics.ts
|
|
141
|
+
function useAdoptionAnalytics(events) {
|
|
142
|
+
return useMemo(() => createAdoptionMetrics(events), [events]);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/react/tour-registry.ts
|
|
146
|
+
var controllers = /* @__PURE__ */ new Map();
|
|
147
|
+
var registryListeners = /* @__PURE__ */ new Map();
|
|
148
|
+
function getTourController(id) {
|
|
149
|
+
return controllers.get(id);
|
|
150
|
+
}
|
|
151
|
+
function subscribeTourRegistry(id, listener) {
|
|
152
|
+
const listeners = registryListeners.get(id) ?? /* @__PURE__ */ new Set();
|
|
153
|
+
listeners.add(listener);
|
|
154
|
+
registryListeners.set(id, listeners);
|
|
155
|
+
return () => {
|
|
156
|
+
const current = registryListeners.get(id);
|
|
157
|
+
if (!current) return;
|
|
158
|
+
current.delete(listener);
|
|
159
|
+
if (current.size === 0) {
|
|
160
|
+
registryListeners.delete(id);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/react/hooks/use-tour.ts
|
|
166
|
+
var EMPTY_SNAPSHOT = {
|
|
167
|
+
isActive: false,
|
|
168
|
+
currentStepIndex: -1,
|
|
169
|
+
currentStep: null,
|
|
170
|
+
totalSteps: 0
|
|
171
|
+
};
|
|
172
|
+
function readSnapshot(id) {
|
|
173
|
+
const controller = getTourController(id);
|
|
174
|
+
if (!controller) return EMPTY_SNAPSHOT;
|
|
175
|
+
return controller.getSnapshot();
|
|
176
|
+
}
|
|
177
|
+
function useTour(id) {
|
|
178
|
+
const [snapshot, setSnapshot] = useState(() => readSnapshot(id));
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
let unsubscribeController = null;
|
|
181
|
+
const bindController = () => {
|
|
182
|
+
if (unsubscribeController) {
|
|
183
|
+
unsubscribeController();
|
|
184
|
+
unsubscribeController = null;
|
|
185
|
+
}
|
|
186
|
+
const controller = getTourController(id);
|
|
187
|
+
if (!controller) {
|
|
188
|
+
setSnapshot(EMPTY_SNAPSHOT);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
setSnapshot(controller.getSnapshot());
|
|
192
|
+
unsubscribeController = controller.subscribe(() => {
|
|
193
|
+
setSnapshot(controller.getSnapshot());
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
bindController();
|
|
197
|
+
const unsubscribeRegistry = subscribeTourRegistry(id, bindController);
|
|
198
|
+
return () => {
|
|
199
|
+
unsubscribeRegistry();
|
|
200
|
+
if (unsubscribeController) unsubscribeController();
|
|
201
|
+
};
|
|
202
|
+
}, [id]);
|
|
203
|
+
const call = useCallback((method) => {
|
|
204
|
+
const controller = getTourController(id);
|
|
205
|
+
if (!controller) return;
|
|
206
|
+
controller[method]();
|
|
207
|
+
}, [id]);
|
|
208
|
+
return useMemo(
|
|
209
|
+
() => ({
|
|
210
|
+
startTour: () => call("startTour"),
|
|
211
|
+
nextStep: () => call("nextStep"),
|
|
212
|
+
prevStep: () => call("prevStep"),
|
|
213
|
+
skipTour: () => call("skipTour"),
|
|
214
|
+
closeTour: () => call("closeTour"),
|
|
215
|
+
currentStep: snapshot.currentStep,
|
|
216
|
+
currentStepIndex: snapshot.currentStepIndex,
|
|
217
|
+
totalSteps: snapshot.totalSteps,
|
|
218
|
+
isActive: snapshot.isActive
|
|
219
|
+
}),
|
|
220
|
+
[call, snapshot]
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
function useTourSequencer(sequence) {
|
|
224
|
+
const {
|
|
225
|
+
newFeatures,
|
|
226
|
+
canShowTour,
|
|
227
|
+
markTourShown,
|
|
228
|
+
markFeatureSeen
|
|
229
|
+
} = useFeatureDrop();
|
|
230
|
+
const [startedFeatureIds, setStartedFeatureIds] = useState(/* @__PURE__ */ new Set());
|
|
231
|
+
const visibleFeatureIds = useMemo(
|
|
232
|
+
() => new Set(newFeatures.map((feature) => feature.id)),
|
|
233
|
+
[newFeatures]
|
|
234
|
+
);
|
|
235
|
+
const remaining = useMemo(
|
|
236
|
+
() => sequence.filter(
|
|
237
|
+
(item) => visibleFeatureIds.has(item.featureId) && !startedFeatureIds.has(item.featureId)
|
|
238
|
+
),
|
|
239
|
+
[sequence, startedFeatureIds, visibleFeatureIds]
|
|
240
|
+
);
|
|
241
|
+
const next = remaining[0] ?? null;
|
|
242
|
+
const startNextTour = useCallback(() => {
|
|
243
|
+
if (!next) return false;
|
|
244
|
+
if (!canShowTour()) return false;
|
|
245
|
+
const controller = getTourController(next.tourId);
|
|
246
|
+
if (!controller) return false;
|
|
247
|
+
setStartedFeatureIds((previous) => {
|
|
248
|
+
if (previous.has(next.featureId)) return previous;
|
|
249
|
+
const updated = new Set(previous);
|
|
250
|
+
updated.add(next.featureId);
|
|
251
|
+
return updated;
|
|
252
|
+
});
|
|
253
|
+
markFeatureSeen(next.featureId);
|
|
254
|
+
markTourShown();
|
|
255
|
+
controller.startTour();
|
|
256
|
+
return true;
|
|
257
|
+
}, [canShowTour, markFeatureSeen, markTourShown, next]);
|
|
258
|
+
return {
|
|
259
|
+
nextTourId: next?.tourId ?? null,
|
|
260
|
+
nextFeatureId: next?.featureId ?? null,
|
|
261
|
+
remainingTours: remaining.length,
|
|
262
|
+
startNextTour
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// src/react/checklist-registry.ts
|
|
267
|
+
var controllers2 = /* @__PURE__ */ new Map();
|
|
268
|
+
var registryListeners2 = /* @__PURE__ */ new Map();
|
|
269
|
+
function getChecklistController(id) {
|
|
270
|
+
return controllers2.get(id);
|
|
271
|
+
}
|
|
272
|
+
function subscribeChecklistRegistry(id, listener) {
|
|
273
|
+
const listeners = registryListeners2.get(id) ?? /* @__PURE__ */ new Set();
|
|
274
|
+
listeners.add(listener);
|
|
275
|
+
registryListeners2.set(id, listeners);
|
|
276
|
+
return () => {
|
|
277
|
+
const current = registryListeners2.get(id);
|
|
278
|
+
if (!current) return;
|
|
279
|
+
current.delete(listener);
|
|
280
|
+
if (current.size === 0) registryListeners2.delete(id);
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/react/hooks/use-checklist.ts
|
|
285
|
+
var EMPTY_SNAPSHOT2 = {
|
|
286
|
+
exists: false,
|
|
287
|
+
tasks: [],
|
|
288
|
+
progress: { completed: 0, total: 0, percent: 0 },
|
|
289
|
+
isComplete: false,
|
|
290
|
+
dismissed: false,
|
|
291
|
+
collapsed: false
|
|
292
|
+
};
|
|
293
|
+
function readSnapshot2(id) {
|
|
294
|
+
const controller = getChecklistController(id);
|
|
295
|
+
if (!controller) return EMPTY_SNAPSHOT2;
|
|
296
|
+
return controller.getSnapshot();
|
|
297
|
+
}
|
|
298
|
+
function useChecklist(id) {
|
|
299
|
+
const [snapshot, setSnapshot] = useState(() => readSnapshot2(id));
|
|
300
|
+
useEffect(() => {
|
|
301
|
+
let unsubscribeController = null;
|
|
302
|
+
const bind = () => {
|
|
303
|
+
if (unsubscribeController) {
|
|
304
|
+
unsubscribeController();
|
|
305
|
+
unsubscribeController = null;
|
|
306
|
+
}
|
|
307
|
+
const controller = getChecklistController(id);
|
|
308
|
+
if (!controller) {
|
|
309
|
+
setSnapshot(EMPTY_SNAPSHOT2);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
setSnapshot(controller.getSnapshot());
|
|
313
|
+
unsubscribeController = controller.subscribe(() => {
|
|
314
|
+
setSnapshot(controller.getSnapshot());
|
|
315
|
+
});
|
|
316
|
+
};
|
|
317
|
+
bind();
|
|
318
|
+
const unsubscribeRegistry = subscribeChecklistRegistry(id, bind);
|
|
319
|
+
return () => {
|
|
320
|
+
unsubscribeRegistry();
|
|
321
|
+
if (unsubscribeController) unsubscribeController();
|
|
322
|
+
};
|
|
323
|
+
}, [id]);
|
|
324
|
+
const invoke = useCallback((method, arg) => {
|
|
325
|
+
const controller = getChecklistController(id);
|
|
326
|
+
if (!controller) return;
|
|
327
|
+
if (method === "completeTask") {
|
|
328
|
+
controller.completeTask(arg ?? "");
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
controller[method]();
|
|
332
|
+
}, [id]);
|
|
333
|
+
return useMemo(
|
|
334
|
+
() => ({
|
|
335
|
+
completeTask: (taskId) => invoke("completeTask", taskId),
|
|
336
|
+
resetChecklist: () => invoke("resetChecklist"),
|
|
337
|
+
dismissChecklist: () => invoke("dismissChecklist"),
|
|
338
|
+
toggleCollapsed: () => invoke("toggleCollapsed"),
|
|
339
|
+
isComplete: snapshot.isComplete,
|
|
340
|
+
progress: snapshot.progress,
|
|
341
|
+
tasks: snapshot.tasks,
|
|
342
|
+
dismissed: snapshot.dismissed,
|
|
343
|
+
collapsed: snapshot.collapsed
|
|
344
|
+
}),
|
|
345
|
+
[invoke, snapshot]
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// src/react/survey-registry.ts
|
|
350
|
+
var controllers3 = /* @__PURE__ */ new Map();
|
|
351
|
+
var registryListeners3 = /* @__PURE__ */ new Map();
|
|
352
|
+
function getSurveyController(id) {
|
|
353
|
+
return controllers3.get(id);
|
|
354
|
+
}
|
|
355
|
+
function subscribeSurveyRegistry(id, listener) {
|
|
356
|
+
const listeners = registryListeners3.get(id) ?? /* @__PURE__ */ new Set();
|
|
357
|
+
listeners.add(listener);
|
|
358
|
+
registryListeners3.set(id, listeners);
|
|
359
|
+
return () => {
|
|
360
|
+
const current = registryListeners3.get(id);
|
|
361
|
+
if (!current) return;
|
|
362
|
+
current.delete(listener);
|
|
363
|
+
if (current.size === 0) {
|
|
364
|
+
registryListeners3.delete(id);
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// src/react/hooks/use-survey.ts
|
|
370
|
+
var EMPTY_SNAPSHOT3 = {
|
|
371
|
+
exists: false,
|
|
372
|
+
isOpen: false,
|
|
373
|
+
submitted: false,
|
|
374
|
+
canShow: false,
|
|
375
|
+
type: "custom"
|
|
376
|
+
};
|
|
377
|
+
function readSnapshot3(id) {
|
|
378
|
+
const controller = getSurveyController(id);
|
|
379
|
+
if (!controller) return EMPTY_SNAPSHOT3;
|
|
380
|
+
return controller.getSnapshot();
|
|
381
|
+
}
|
|
382
|
+
function useSurvey(id) {
|
|
383
|
+
const [snapshot, setSnapshot] = useState(() => readSnapshot3(id));
|
|
384
|
+
useEffect(() => {
|
|
385
|
+
let unsubscribeController = null;
|
|
386
|
+
const bind = () => {
|
|
387
|
+
if (unsubscribeController) {
|
|
388
|
+
unsubscribeController();
|
|
389
|
+
unsubscribeController = null;
|
|
390
|
+
}
|
|
391
|
+
const controller = getSurveyController(id);
|
|
392
|
+
if (!controller) {
|
|
393
|
+
setSnapshot(EMPTY_SNAPSHOT3);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
setSnapshot(controller.getSnapshot());
|
|
397
|
+
unsubscribeController = controller.subscribe(() => {
|
|
398
|
+
setSnapshot(controller.getSnapshot());
|
|
399
|
+
});
|
|
400
|
+
};
|
|
401
|
+
bind();
|
|
402
|
+
const unsubscribeRegistry = subscribeSurveyRegistry(id, bind);
|
|
403
|
+
return () => {
|
|
404
|
+
unsubscribeRegistry();
|
|
405
|
+
if (unsubscribeController) unsubscribeController();
|
|
406
|
+
};
|
|
407
|
+
}, [id]);
|
|
408
|
+
const show = useCallback((options) => {
|
|
409
|
+
const controller = getSurveyController(id);
|
|
410
|
+
if (!controller) return false;
|
|
411
|
+
return controller.show(options);
|
|
412
|
+
}, [id]);
|
|
413
|
+
const hide = useCallback(() => {
|
|
414
|
+
getSurveyController(id)?.hide();
|
|
415
|
+
}, [id]);
|
|
416
|
+
const askLater = useCallback(() => {
|
|
417
|
+
getSurveyController(id)?.askLater();
|
|
418
|
+
}, [id]);
|
|
419
|
+
return useMemo(
|
|
420
|
+
() => ({
|
|
421
|
+
show,
|
|
422
|
+
hide,
|
|
423
|
+
askLater,
|
|
424
|
+
isOpen: snapshot.isOpen,
|
|
425
|
+
submitted: snapshot.submitted,
|
|
426
|
+
canShow: snapshot.canShow,
|
|
427
|
+
type: snapshot.type
|
|
428
|
+
}),
|
|
429
|
+
[askLater, hide, show, snapshot]
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
function useChangelog() {
|
|
433
|
+
const ctx = useFeatureDrop();
|
|
434
|
+
const markAllSeen = useCallback(() => {
|
|
435
|
+
void ctx.dismissAll();
|
|
436
|
+
}, [ctx]);
|
|
437
|
+
const getByCategory = useCallback(
|
|
438
|
+
(category) => {
|
|
439
|
+
return ctx.newFeatures.filter((f) => f.category === category);
|
|
440
|
+
},
|
|
441
|
+
[ctx.newFeatures]
|
|
442
|
+
);
|
|
443
|
+
return useMemo(
|
|
444
|
+
() => ({
|
|
445
|
+
features: ctx.manifest,
|
|
446
|
+
newFeatures: ctx.newFeatures,
|
|
447
|
+
newCount: ctx.newCount,
|
|
448
|
+
newFeaturesSorted: ctx.newFeaturesSorted,
|
|
449
|
+
dismiss: ctx.dismiss,
|
|
450
|
+
dismissAll: () => void ctx.dismissAll(),
|
|
451
|
+
isNew: ctx.isNew,
|
|
452
|
+
markAllSeen,
|
|
453
|
+
getByCategory
|
|
454
|
+
}),
|
|
455
|
+
[ctx.manifest, ctx.newFeatures, ctx.newCount, ctx.newFeaturesSorted, ctx.dismiss, ctx.dismissAll, ctx.isNew, markAllSeen, getByCategory]
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export { useAdoptionAnalytics, useChangelog, useChecklist, useFeatureDrop, useNewCount, useNewFeature, useSurvey, useTabNotification, useTour, useTourSequencer };
|
|
460
|
+
//# sourceMappingURL=react-hooks.js.map
|
|
461
|
+
//# sourceMappingURL=react-hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react/context.ts","../src/react/hooks/use-feature-drop.ts","../src/react/hooks/use-new-feature.ts","../src/react/hooks/use-new-count.ts","../src/react/hooks/use-tab-notification.ts","../src/analytics.ts","../src/react/hooks/use-adoption-analytics.ts","../src/react/tour-registry.ts","../src/react/hooks/use-tour.ts","../src/react/hooks/use-tour-sequencer.ts","../src/react/checklist-registry.ts","../src/react/hooks/use-checklist.ts","../src/react/survey-registry.ts","../src/react/hooks/use-survey.ts","../src/react/hooks/use-changelog.ts"],"names":["useEffect","useMemo","useState","useCallback","controllers","registryListeners","EMPTY_SNAPSHOT","readSnapshot"],"mappings":"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","file":"react-hooks.js","sourcesContent":["import { createContext } from \"react\";\nimport type { FeatureDropAnimationPreset, FeatureDropEngine, FeatureEntry, FeaturePriority } from \"../types\";\nimport type { AdoptionEventInput } from \"../analytics\";\nimport type { FeatureDropTranslations } from \"../i18n\";\n\nexport interface FeatureDropContextValue {\n /** Full manifest provided to the provider */\n manifest: FeatureEntry[] | readonly FeatureEntry[];\n /** All currently \"new\" features */\n newFeatures: FeatureEntry[];\n /** New features currently queued by throttling rules */\n queuedFeatures: FeatureEntry[];\n /** Count of new features */\n newCount: number;\n /** Count before throttling (all pending new features) */\n totalNewCount: number;\n /** All new features sorted by priority then release date */\n newFeaturesSorted: FeatureEntry[];\n /** Check if a sidebar key has any new features */\n isNew: (sidebarKey: string) => boolean;\n /** Dismiss a single feature by ID */\n dismiss: (id: string) => void;\n /** Dismiss all features (marks all as seen) */\n dismissAll: () => Promise<void>;\n /** Get the feature entry for a sidebar key (if it's new) */\n getFeature: (sidebarKey: string) => FeatureEntry | undefined;\n /** Whether quiet mode (Do Not Disturb) is enabled */\n quietMode: boolean;\n /** Enable/disable quiet mode */\n setQuietMode: (enabled: boolean) => void;\n /** Mark a feature as seen for dependency-chain resolution */\n markFeatureSeen: (featureId: string) => void;\n /** Mark a feature as clicked for dependency-chain resolution */\n markFeatureClicked: (featureId: string) => void;\n /** Remaining toasts allowed in this session under throttle rules */\n getRemainingToastSlots: () => number;\n /** Mark toasts as shown in this session */\n markToastsShown: (featureIds: string[]) => void;\n /** Whether a modal can open right now under throttle rules */\n canShowModal: (priority?: FeaturePriority) => boolean;\n /** Record a modal display timestamp */\n markModalShown: () => void;\n /** Whether a tour can start right now under throttle rules */\n canShowTour: () => boolean;\n /** Record a tour start timestamp */\n markTourShown: () => void;\n /** Acquire/release spotlight slots under maxSimultaneousSpotlights */\n acquireSpotlightSlot: (id: string, priority?: FeaturePriority) => boolean;\n releaseSpotlightSlot: (id: string) => void;\n /** Number of currently active spotlight slots */\n activeSpotlightCount: number;\n /** Emit an adoption analytics event (collector-backed when configured) */\n trackAdoptionEvent: (event: AdoptionEventInput) => void;\n /** Report a component/runtime error to provider-level monitoring hooks */\n reportError: (\n error: unknown,\n context?: { component?: string; componentStack?: string },\n ) => void;\n /** Active locale code used by built-in UI strings */\n locale: string;\n /** Text direction derived from locale */\n direction: \"ltr\" | \"rtl\";\n /** Active motion preset for built-in component transitions */\n animation: FeatureDropAnimationPreset;\n /** Resolved translation strings for built-in React components */\n translations: FeatureDropTranslations;\n /** Track a named usage event for trigger rules */\n trackUsageEvent: (event: string, delta?: number) => void;\n /** Track a named trigger event for trigger rules */\n trackTriggerEvent: (event: string) => void;\n /** Mark a milestone for trigger rules */\n trackMilestone: (event: string) => void;\n /** Manually override current path for page trigger rules */\n setTriggerPath: (path: string) => void;\n /** Optional engine instance for AI-powered delivery intelligence */\n engine: FeatureDropEngine | null;\n}\n\nexport const FeatureDropContext = createContext<FeatureDropContextValue | null>(\n null,\n);\n","import { useContext } from \"react\";\nimport { FeatureDropContext } from \"../context\";\nimport type { FeatureDropContextValue } from \"../context\";\n\n/**\n * Access the full feature discovery context.\n *\n * Returns: `{ newFeatures, newCount, isNew, dismiss, dismissAll, getFeature }`\n *\n * @throws Error if used outside of `<FeatureDropProvider>`\n */\nexport function useFeatureDrop(): FeatureDropContextValue {\n const context = useContext(FeatureDropContext);\n if (!context) {\n throw new Error(\n \"useFeatureDrop must be used within a <FeatureDropProvider>\",\n );\n }\n return context;\n}\n","import { useFeatureDrop } from \"./use-feature-drop\";\nimport type { FeatureEntry } from \"../../types\";\n\nexport interface UseNewFeatureResult {\n /** Whether this sidebar key has a new feature */\n isNew: boolean;\n /** The feature entry, if new */\n feature: FeatureEntry | undefined;\n /** Dismiss the feature for this sidebar key */\n dismiss: () => void;\n}\n\n/**\n * Check if a single navigation item has a new feature.\n *\n * @param sidebarKey - The key to check (e.g. \"/journal\", \"settings\")\n * @returns `{ isNew, feature, dismiss }`\n */\nexport function useNewFeature(sidebarKey: string): UseNewFeatureResult {\n const { isNew, getFeature, dismiss } = useFeatureDrop();\n\n const feature = getFeature(sidebarKey);\n const isNewValue = isNew(sidebarKey);\n\n return {\n isNew: isNewValue,\n feature,\n dismiss: () => {\n if (feature) {\n dismiss(feature.id);\n }\n },\n };\n}\n","import { useFeatureDrop } from \"./use-feature-drop\";\n\n/**\n * Get the count of currently new features.\n *\n * Useful for rendering a badge count on a \"What's New\" button.\n *\n * @returns The number of new features\n */\nexport function useNewCount(): number {\n const { newCount } = useFeatureDrop();\n return newCount;\n}\n","import { useEffect, useRef } from \"react\";\nimport { useFeatureDrop } from \"./use-feature-drop\";\n\nexport interface UseTabNotificationOptions {\n /** Whether tab notifications are enabled. Default: true */\n enabled?: boolean;\n /** Template string. `{count}` is replaced with the number. Default: \"({count}) {title}\" */\n template?: string;\n /** Enable flashing/blinking pattern for attention. Default: false */\n flash?: boolean;\n /** Flash interval in ms. Default: 1500 */\n flashInterval?: number;\n}\n\n/**\n * Updates the browser tab title with the unread feature count.\n *\n * Shows \"(3) My App\" when there are new features, restores the original\n * title when all features are read. Optional flash/blink pattern for attention.\n *\n * @example\n * ```tsx\n * function App() {\n * useTabNotification();\n * return <div>...</div>;\n * }\n * ```\n *\n * @example With flash\n * ```tsx\n * useTabNotification({ flash: true, template: \"[{count} new] {title}\" });\n * ```\n */\nexport function useTabNotification(options: UseTabNotificationOptions = {}): void {\n const {\n enabled = true,\n template = \"({count}) {title}\",\n flash = false,\n flashInterval = 1500,\n } = options;\n\n const { newCount } = useFeatureDrop();\n const originalTitleRef = useRef<string>(\"\");\n const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n\n // Capture original title once\n if (!originalTitleRef.current) {\n originalTitleRef.current = document.title;\n }\n const originalTitle = originalTitleRef.current;\n\n if (!enabled || newCount === 0) {\n // Restore original title\n document.title = originalTitle;\n if (intervalRef.current) {\n clearInterval(intervalRef.current);\n intervalRef.current = null;\n }\n return;\n }\n\n const notificationTitle = template\n .replace(\"{count}\", String(newCount))\n .replace(\"{title}\", originalTitle);\n\n if (flash) {\n // Alternate between notification and original title\n let showNotification = true;\n document.title = notificationTitle;\n intervalRef.current = setInterval(() => {\n showNotification = !showNotification;\n document.title = showNotification ? notificationTitle : originalTitle;\n }, flashInterval);\n } else {\n document.title = notificationTitle;\n }\n\n return () => {\n document.title = originalTitle;\n if (intervalRef.current) {\n clearInterval(intervalRef.current);\n intervalRef.current = null;\n }\n };\n }, [enabled, newCount, template, flash, flashInterval]);\n}\n","export type AdoptionEventType =\n | \"feature_seen\"\n | \"feature_clicked\"\n | \"feature_dismissed\"\n | \"tour_started\"\n | \"tour_completed\"\n | \"tour_skipped\"\n | \"checklist_task_completed\"\n | \"checklist_completed\"\n | \"survey_submitted\"\n | \"feedback_submitted\"\n | \"announcement_shown\"\n | \"cta_clicked\";\n\nexport interface AdoptionEvent {\n type: AdoptionEventType;\n featureId?: string;\n tourId?: string;\n variant?: string;\n timestamp: string;\n sessionId?: string;\n userId?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport type AdoptionEventInput = Omit<AdoptionEvent, \"timestamp\"> & {\n timestamp?: string;\n};\n\nexport interface AnalyticsAdapter {\n track: (event: AdoptionEvent) => void | Promise<void>;\n trackBatch?: (events: AdoptionEvent[]) => void | Promise<void>;\n}\n\nexport interface AnalyticsCollectorOptions {\n adapter: AnalyticsAdapter;\n batchSize?: number;\n flushInterval?: number;\n sampleRate?: number;\n enabled?: boolean;\n sessionId?: string;\n userId?: string;\n now?: () => Date;\n random?: () => number;\n}\n\nexport class AnalyticsCollector {\n private adapter: AnalyticsAdapter;\n private queue: AdoptionEvent[] = [];\n private batchSize: number;\n private flushInterval: number;\n private sampleRate: number;\n private enabled: boolean;\n private now: () => Date;\n private random: () => number;\n private sessionId?: string;\n private userId?: string;\n private timer: ReturnType<typeof setInterval> | null = null;\n private flushing = false;\n\n constructor(options: AnalyticsCollectorOptions) {\n this.adapter = options.adapter;\n this.batchSize = options.batchSize ?? 20;\n this.flushInterval = options.flushInterval ?? 10_000;\n this.sampleRate = options.sampleRate ?? 1;\n this.enabled = options.enabled ?? true;\n this.sessionId = options.sessionId;\n this.userId = options.userId;\n this.now = options.now ?? (() => new Date());\n this.random = options.random ?? Math.random;\n this.startTimer();\n }\n\n setEnabled(enabled: boolean): void {\n this.enabled = enabled;\n }\n\n setContext(context: { sessionId?: string; userId?: string }): void {\n if (context.sessionId !== undefined) this.sessionId = context.sessionId;\n if (context.userId !== undefined) this.userId = context.userId;\n }\n\n getQueueSize(): number {\n return this.queue.length;\n }\n\n track(event: AdoptionEventInput): void {\n if (!this.enabled) return;\n if (this.sampleRate < 1 && this.random() > this.sampleRate) return;\n const normalized: AdoptionEvent = {\n ...event,\n timestamp: event.timestamp ?? this.now().toISOString(),\n sessionId: event.sessionId ?? this.sessionId,\n userId: event.userId ?? this.userId,\n };\n this.queue.push(normalized);\n if (this.queue.length >= this.batchSize) {\n void this.flush();\n }\n }\n\n async flush(): Promise<void> {\n if (this.flushing) return;\n if (this.queue.length === 0) return;\n this.flushing = true;\n const batch = this.queue.splice(0, this.queue.length);\n try {\n if (this.adapter.trackBatch) {\n await this.adapter.trackBatch(batch);\n } else {\n for (const event of batch) {\n await this.adapter.track(event);\n }\n }\n } catch {\n // Requeue on transient failures.\n this.queue = [...batch, ...this.queue];\n } finally {\n this.flushing = false;\n }\n }\n\n async destroy(): Promise<void> {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n await this.flush();\n }\n\n private startTimer(): void {\n if (this.flushInterval <= 0) return;\n this.timer = setInterval(() => {\n void this.flush();\n }, this.flushInterval);\n }\n}\n\nexport class PostHogAdapter implements AnalyticsAdapter {\n constructor(\n private readonly client: {\n capture: (event: string, properties?: Record<string, unknown>) => void;\n },\n ) {}\n\n track(event: AdoptionEvent): void {\n this.client.capture(event.type, {\n featureId: event.featureId,\n tourId: event.tourId,\n variant: event.variant,\n timestamp: event.timestamp,\n sessionId: event.sessionId,\n userId: event.userId,\n ...event.metadata,\n });\n }\n}\n\nexport class AmplitudeAdapter implements AnalyticsAdapter {\n constructor(\n private readonly client: {\n track: (event: string, properties?: Record<string, unknown>) => void;\n },\n ) {}\n\n track(event: AdoptionEvent): void {\n this.client.track(event.type, {\n featureId: event.featureId,\n tourId: event.tourId,\n variant: event.variant,\n timestamp: event.timestamp,\n sessionId: event.sessionId,\n userId: event.userId,\n ...event.metadata,\n });\n }\n}\n\nexport class MixpanelAdapter implements AnalyticsAdapter {\n constructor(\n private readonly client: {\n track: (event: string, properties?: Record<string, unknown>) => void;\n },\n ) {}\n\n track(event: AdoptionEvent): void {\n this.client.track(event.type, {\n featureId: event.featureId,\n tourId: event.tourId,\n variant: event.variant,\n timestamp: event.timestamp,\n sessionId: event.sessionId,\n userId: event.userId,\n ...event.metadata,\n });\n }\n}\n\nexport class SegmentAdapter implements AnalyticsAdapter {\n constructor(\n private readonly client: {\n track: (event: string, properties?: Record<string, unknown>) => void;\n },\n ) {}\n\n track(event: AdoptionEvent): void {\n this.client.track(event.type, {\n featureId: event.featureId,\n tourId: event.tourId,\n variant: event.variant,\n timestamp: event.timestamp,\n sessionId: event.sessionId,\n userId: event.userId,\n ...event.metadata,\n });\n }\n}\n\nexport class CustomAdapter implements AnalyticsAdapter {\n constructor(private readonly handler: (event: AdoptionEvent) => void | Promise<void>) {}\n\n track(event: AdoptionEvent): void | Promise<void> {\n return this.handler(event);\n }\n}\n\nexport interface FeatureEngagementMetrics {\n seen: number;\n clicked: number;\n dismissed: number;\n}\n\nexport interface AdoptionMetrics {\n getAdoptionRate: (featureId: string) => number;\n getTourCompletionRate: (tourId: string) => number;\n getChecklistCompletionRate: (checklistId: string) => number;\n getFeatureEngagement: (featureId: string) => FeatureEngagementMetrics;\n getVariantPerformance: (featureId: string) => Record<string, number>;\n}\n\nexport function createAdoptionMetrics(events: AdoptionEvent[]): AdoptionMetrics {\n const getAdoptionRate = (featureId: string): number => {\n const seen = events.filter((event) => event.type === \"feature_seen\" && event.featureId === featureId).length;\n if (seen === 0) return 0;\n const clicked = events.filter((event) => event.type === \"feature_clicked\" && event.featureId === featureId).length;\n return clicked / seen;\n };\n\n const getTourCompletionRate = (tourId: string): number => {\n const started = events.filter((event) => event.type === \"tour_started\" && event.tourId === tourId).length;\n if (started === 0) return 0;\n const completed = events.filter((event) => event.type === \"tour_completed\" && event.tourId === tourId).length;\n return completed / started;\n };\n\n const getChecklistCompletionRate = (checklistId: string): number => {\n const taskCompleted = events.filter((event) =>\n event.type === \"checklist_task_completed\" &&\n event.metadata?.checklistId === checklistId\n ).length;\n if (taskCompleted === 0) return 0;\n const completed = events.filter((event) =>\n event.type === \"checklist_completed\" &&\n event.metadata?.checklistId === checklistId\n ).length;\n return completed / taskCompleted;\n };\n\n const getFeatureEngagement = (featureId: string): FeatureEngagementMetrics => ({\n seen: events.filter((event) => event.type === \"feature_seen\" && event.featureId === featureId).length,\n clicked: events.filter((event) => event.type === \"feature_clicked\" && event.featureId === featureId).length,\n dismissed: events.filter((event) => event.type === \"feature_dismissed\" && event.featureId === featureId).length,\n });\n\n const getVariantPerformance = (featureId: string): Record<string, number> => {\n const byVariant = new Map<string, { seen: number; clicked: number }>();\n for (const event of events) {\n if (event.featureId !== featureId) continue;\n const variant = event.variant ?? \"control\";\n const bucket = byVariant.get(variant) ?? { seen: 0, clicked: 0 };\n if (event.type === \"feature_seen\") bucket.seen += 1;\n if (event.type === \"feature_clicked\") bucket.clicked += 1;\n byVariant.set(variant, bucket);\n }\n const output: Record<string, number> = {};\n for (const [variant, bucket] of byVariant.entries()) {\n output[variant] = bucket.seen === 0 ? 0 : bucket.clicked / bucket.seen;\n }\n return output;\n };\n\n return {\n getAdoptionRate,\n getTourCompletionRate,\n getChecklistCompletionRate,\n getFeatureEngagement,\n getVariantPerformance,\n };\n}\n","import { useMemo } from \"react\";\nimport type { AdoptionEvent } from \"../../analytics\";\nimport { createAdoptionMetrics, type AdoptionMetrics } from \"../../analytics\";\n\nexport function useAdoptionAnalytics(events: AdoptionEvent[]): AdoptionMetrics {\n return useMemo(() => createAdoptionMetrics(events), [events]);\n}\n","import type { TourStep } from \"./components/tour\";\n\nexport interface TourSnapshot {\n isActive: boolean;\n currentStepIndex: number;\n currentStep: TourStep | null;\n totalSteps: number;\n}\n\nexport interface TourController {\n startTour: () => void;\n nextStep: () => void;\n prevStep: () => void;\n skipTour: () => void;\n closeTour: () => void;\n getSnapshot: () => TourSnapshot;\n subscribe: (listener: () => void) => () => void;\n}\n\nconst controllers = new Map<string, TourController>();\nconst registryListeners = new Map<string, Set<() => void>>();\n\nfunction emitRegistry(id: string): void {\n const listeners = registryListeners.get(id);\n if (!listeners) return;\n for (const listener of listeners) listener();\n}\n\nexport function registerTourController(id: string, controller: TourController): () => void {\n controllers.set(id, controller);\n emitRegistry(id);\n return () => {\n if (controllers.get(id) === controller) {\n controllers.delete(id);\n emitRegistry(id);\n }\n };\n}\n\nexport function getTourController(id: string): TourController | undefined {\n return controllers.get(id);\n}\n\nexport function subscribeTourRegistry(id: string, listener: () => void): () => void {\n const listeners = registryListeners.get(id) ?? new Set<() => void>();\n listeners.add(listener);\n registryListeners.set(id, listeners);\n return () => {\n const current = registryListeners.get(id);\n if (!current) return;\n current.delete(listener);\n if (current.size === 0) {\n registryListeners.delete(id);\n }\n };\n}\n","import { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { getTourController, subscribeTourRegistry, type TourSnapshot } from \"../tour-registry\";\n\nconst EMPTY_SNAPSHOT: TourSnapshot = {\n isActive: false,\n currentStepIndex: -1,\n currentStep: null,\n totalSteps: 0,\n};\n\nexport interface UseTourResult {\n startTour: () => void;\n nextStep: () => void;\n prevStep: () => void;\n skipTour: () => void;\n closeTour: () => void;\n currentStep: TourSnapshot[\"currentStep\"];\n currentStepIndex: number;\n totalSteps: number;\n isActive: boolean;\n}\n\nfunction readSnapshot(id: string): TourSnapshot {\n const controller = getTourController(id);\n if (!controller) return EMPTY_SNAPSHOT;\n return controller.getSnapshot();\n}\n\nexport function useTour(id: string): UseTourResult {\n const [snapshot, setSnapshot] = useState<TourSnapshot>(() => readSnapshot(id));\n\n useEffect(() => {\n let unsubscribeController: (() => void) | null = null;\n\n const bindController = (): void => {\n if (unsubscribeController) {\n unsubscribeController();\n unsubscribeController = null;\n }\n const controller = getTourController(id);\n if (!controller) {\n setSnapshot(EMPTY_SNAPSHOT);\n return;\n }\n setSnapshot(controller.getSnapshot());\n unsubscribeController = controller.subscribe(() => {\n setSnapshot(controller.getSnapshot());\n });\n };\n\n bindController();\n const unsubscribeRegistry = subscribeTourRegistry(id, bindController);\n return () => {\n unsubscribeRegistry();\n if (unsubscribeController) unsubscribeController();\n };\n }, [id]);\n\n const call = useCallback((method: keyof Omit<UseTourResult, \"currentStep\" | \"currentStepIndex\" | \"totalSteps\" | \"isActive\">) => {\n const controller = getTourController(id);\n if (!controller) return;\n controller[method]();\n }, [id]);\n\n return useMemo(\n () => ({\n startTour: () => call(\"startTour\"),\n nextStep: () => call(\"nextStep\"),\n prevStep: () => call(\"prevStep\"),\n skipTour: () => call(\"skipTour\"),\n closeTour: () => call(\"closeTour\"),\n currentStep: snapshot.currentStep,\n currentStepIndex: snapshot.currentStepIndex,\n totalSteps: snapshot.totalSteps,\n isActive: snapshot.isActive,\n }),\n [call, snapshot],\n );\n}\n","import { useCallback, useMemo, useState } from \"react\";\nimport { useFeatureDrop } from \"./use-feature-drop\";\nimport { getTourController } from \"../tour-registry\";\n\nexport interface TourSequenceItem {\n featureId: string;\n tourId: string;\n}\n\nexport interface UseTourSequencerResult {\n nextTourId: string | null;\n nextFeatureId: string | null;\n remainingTours: number;\n startNextTour: () => boolean;\n}\n\nexport function useTourSequencer(sequence: TourSequenceItem[]): UseTourSequencerResult {\n const {\n newFeatures,\n canShowTour,\n markTourShown,\n markFeatureSeen,\n } = useFeatureDrop();\n const [startedFeatureIds, setStartedFeatureIds] = useState<Set<string>>(new Set());\n\n const visibleFeatureIds = useMemo(\n () => new Set(newFeatures.map((feature) => feature.id)),\n [newFeatures],\n );\n\n const remaining = useMemo(\n () =>\n sequence.filter(\n (item) =>\n visibleFeatureIds.has(item.featureId) &&\n !startedFeatureIds.has(item.featureId),\n ),\n [sequence, startedFeatureIds, visibleFeatureIds],\n );\n\n const next = remaining[0] ?? null;\n\n const startNextTour = useCallback((): boolean => {\n if (!next) return false;\n if (!canShowTour()) return false;\n const controller = getTourController(next.tourId);\n if (!controller) return false;\n setStartedFeatureIds((previous) => {\n if (previous.has(next.featureId)) return previous;\n const updated = new Set(previous);\n updated.add(next.featureId);\n return updated;\n });\n markFeatureSeen(next.featureId);\n markTourShown();\n controller.startTour();\n return true;\n }, [canShowTour, markFeatureSeen, markTourShown, next]);\n\n return {\n nextTourId: next?.tourId ?? null,\n nextFeatureId: next?.featureId ?? null,\n remainingTours: remaining.length,\n startNextTour,\n };\n}\n","export interface ChecklistSnapshot {\n exists: boolean;\n tasks: Array<{ id: string; completed: boolean }>;\n progress: { completed: number; total: number; percent: number };\n isComplete: boolean;\n dismissed: boolean;\n collapsed: boolean;\n}\n\nexport interface ChecklistController {\n completeTask: (taskId: string) => void;\n resetChecklist: () => void;\n dismissChecklist: () => void;\n toggleCollapsed: () => void;\n getSnapshot: () => ChecklistSnapshot;\n subscribe: (listener: () => void) => () => void;\n}\n\nconst controllers = new Map<string, ChecklistController>();\nconst registryListeners = new Map<string, Set<() => void>>();\n\nfunction emitRegistry(id: string): void {\n const listeners = registryListeners.get(id);\n if (!listeners) return;\n for (const listener of listeners) listener();\n}\n\nexport function registerChecklistController(id: string, controller: ChecklistController): () => void {\n controllers.set(id, controller);\n emitRegistry(id);\n return () => {\n if (controllers.get(id) === controller) {\n controllers.delete(id);\n emitRegistry(id);\n }\n };\n}\n\nexport function getChecklistController(id: string): ChecklistController | undefined {\n return controllers.get(id);\n}\n\nexport function subscribeChecklistRegistry(id: string, listener: () => void): () => void {\n const listeners = registryListeners.get(id) ?? new Set<() => void>();\n listeners.add(listener);\n registryListeners.set(id, listeners);\n return () => {\n const current = registryListeners.get(id);\n if (!current) return;\n current.delete(listener);\n if (current.size === 0) registryListeners.delete(id);\n };\n}\n","import { useCallback, useEffect, useMemo, useState } from \"react\";\nimport {\n getChecklistController,\n subscribeChecklistRegistry,\n type ChecklistSnapshot,\n} from \"../checklist-registry\";\n\nconst EMPTY_SNAPSHOT: ChecklistSnapshot = {\n exists: false,\n tasks: [],\n progress: { completed: 0, total: 0, percent: 0 },\n isComplete: false,\n dismissed: false,\n collapsed: false,\n};\n\nexport interface UseChecklistResult {\n completeTask: (taskId: string) => void;\n resetChecklist: () => void;\n dismissChecklist: () => void;\n toggleCollapsed: () => void;\n isComplete: boolean;\n progress: { completed: number; total: number; percent: number };\n tasks: Array<{ id: string; completed: boolean }>;\n dismissed: boolean;\n collapsed: boolean;\n}\n\nfunction readSnapshot(id: string): ChecklistSnapshot {\n const controller = getChecklistController(id);\n if (!controller) return EMPTY_SNAPSHOT;\n return controller.getSnapshot();\n}\n\nexport function useChecklist(id: string): UseChecklistResult {\n const [snapshot, setSnapshot] = useState<ChecklistSnapshot>(() => readSnapshot(id));\n\n useEffect(() => {\n let unsubscribeController: (() => void) | null = null;\n\n const bind = (): void => {\n if (unsubscribeController) {\n unsubscribeController();\n unsubscribeController = null;\n }\n const controller = getChecklistController(id);\n if (!controller) {\n setSnapshot(EMPTY_SNAPSHOT);\n return;\n }\n setSnapshot(controller.getSnapshot());\n unsubscribeController = controller.subscribe(() => {\n setSnapshot(controller.getSnapshot());\n });\n };\n\n bind();\n const unsubscribeRegistry = subscribeChecklistRegistry(id, bind);\n return () => {\n unsubscribeRegistry();\n if (unsubscribeController) unsubscribeController();\n };\n }, [id]);\n\n const invoke = useCallback((method: \"completeTask\" | \"resetChecklist\" | \"dismissChecklist\" | \"toggleCollapsed\", arg?: string) => {\n const controller = getChecklistController(id);\n if (!controller) return;\n if (method === \"completeTask\") {\n controller.completeTask(arg ?? \"\");\n return;\n }\n controller[method]();\n }, [id]);\n\n return useMemo(\n () => ({\n completeTask: (taskId: string) => invoke(\"completeTask\", taskId),\n resetChecklist: () => invoke(\"resetChecklist\"),\n dismissChecklist: () => invoke(\"dismissChecklist\"),\n toggleCollapsed: () => invoke(\"toggleCollapsed\"),\n isComplete: snapshot.isComplete,\n progress: snapshot.progress,\n tasks: snapshot.tasks,\n dismissed: snapshot.dismissed,\n collapsed: snapshot.collapsed,\n }),\n [invoke, snapshot],\n );\n}\n","import type { SurveyType } from \"./components/survey\";\n\nexport interface SurveySnapshot {\n exists: boolean;\n isOpen: boolean;\n submitted: boolean;\n canShow: boolean;\n type: SurveyType;\n}\n\nexport interface SurveyController {\n show: (options?: { force?: boolean }) => boolean;\n hide: () => void;\n askLater: () => void;\n getSnapshot: () => SurveySnapshot;\n subscribe: (listener: () => void) => () => void;\n}\n\nconst controllers = new Map<string, SurveyController>();\nconst registryListeners = new Map<string, Set<() => void>>();\n\nfunction emitRegistry(id: string): void {\n const listeners = registryListeners.get(id);\n if (!listeners) return;\n for (const listener of listeners) listener();\n}\n\nexport function registerSurveyController(id: string, controller: SurveyController): () => void {\n controllers.set(id, controller);\n emitRegistry(id);\n return () => {\n if (controllers.get(id) === controller) {\n controllers.delete(id);\n emitRegistry(id);\n }\n };\n}\n\nexport function getSurveyController(id: string): SurveyController | undefined {\n return controllers.get(id);\n}\n\nexport function subscribeSurveyRegistry(id: string, listener: () => void): () => void {\n const listeners = registryListeners.get(id) ?? new Set<() => void>();\n listeners.add(listener);\n registryListeners.set(id, listeners);\n return () => {\n const current = registryListeners.get(id);\n if (!current) return;\n current.delete(listener);\n if (current.size === 0) {\n registryListeners.delete(id);\n }\n };\n}\n","import { useCallback, useEffect, useMemo, useState } from \"react\";\nimport {\n getSurveyController,\n subscribeSurveyRegistry,\n type SurveySnapshot,\n} from \"../survey-registry\";\nimport type { SurveyType } from \"../components/survey\";\n\nconst EMPTY_SNAPSHOT: SurveySnapshot = {\n exists: false,\n isOpen: false,\n submitted: false,\n canShow: false,\n type: \"custom\",\n};\n\nexport interface UseSurveyResult {\n show: (options?: { force?: boolean }) => boolean;\n hide: () => void;\n askLater: () => void;\n isOpen: boolean;\n submitted: boolean;\n canShow: boolean;\n type: SurveyType;\n}\n\nfunction readSnapshot(id: string): SurveySnapshot {\n const controller = getSurveyController(id);\n if (!controller) return EMPTY_SNAPSHOT;\n return controller.getSnapshot();\n}\n\nexport function useSurvey(id: string): UseSurveyResult {\n const [snapshot, setSnapshot] = useState<SurveySnapshot>(() => readSnapshot(id));\n\n useEffect(() => {\n let unsubscribeController: (() => void) | null = null;\n\n const bind = (): void => {\n if (unsubscribeController) {\n unsubscribeController();\n unsubscribeController = null;\n }\n const controller = getSurveyController(id);\n if (!controller) {\n setSnapshot(EMPTY_SNAPSHOT);\n return;\n }\n setSnapshot(controller.getSnapshot());\n unsubscribeController = controller.subscribe(() => {\n setSnapshot(controller.getSnapshot());\n });\n };\n\n bind();\n const unsubscribeRegistry = subscribeSurveyRegistry(id, bind);\n return () => {\n unsubscribeRegistry();\n if (unsubscribeController) unsubscribeController();\n };\n }, [id]);\n\n const show = useCallback((options?: { force?: boolean }): boolean => {\n const controller = getSurveyController(id);\n if (!controller) return false;\n return controller.show(options);\n }, [id]);\n\n const hide = useCallback(() => {\n getSurveyController(id)?.hide();\n }, [id]);\n\n const askLater = useCallback(() => {\n getSurveyController(id)?.askLater();\n }, [id]);\n\n return useMemo(\n () => ({\n show,\n hide,\n askLater,\n isOpen: snapshot.isOpen,\n submitted: snapshot.submitted,\n canShow: snapshot.canShow,\n type: snapshot.type,\n }),\n [askLater, hide, show, snapshot],\n );\n}\n","import { useCallback, useMemo } from \"react\";\nimport { useFeatureDrop } from \"./use-feature-drop\";\nimport type { FeatureEntry } from \"../../types\";\n\nexport interface UseChangelogResult {\n /** All features from the manifest (including non-new) */\n features: readonly FeatureEntry[];\n /** Only features that are currently \"new\" (unread) */\n newFeatures: readonly FeatureEntry[];\n /** Count of new/unread features */\n newCount: number;\n /** Sorted new features (critical first, then by date) */\n newFeaturesSorted: readonly FeatureEntry[];\n /** Dismiss a single feature by ID */\n dismiss: (id: string) => void;\n /** Dismiss all features at once */\n dismissAll: () => void;\n /** Check if a specific feature is new */\n isNew: (sidebarKey: string) => boolean;\n /** Mark all currently visible features as seen (advances watermark) */\n markAllSeen: () => void;\n /** Get features filtered by category */\n getByCategory: (category: string) => readonly FeatureEntry[];\n}\n\nexport function useChangelog(): UseChangelogResult {\n const ctx = useFeatureDrop();\n\n const markAllSeen = useCallback(() => {\n void ctx.dismissAll();\n }, [ctx]);\n\n const getByCategory = useCallback(\n (category: string): readonly FeatureEntry[] => {\n return ctx.newFeatures.filter((f) => f.category === category);\n },\n [ctx.newFeatures],\n );\n\n return useMemo(\n () => ({\n features: ctx.manifest,\n newFeatures: ctx.newFeatures,\n newCount: ctx.newCount,\n newFeaturesSorted: ctx.newFeaturesSorted,\n dismiss: ctx.dismiss,\n dismissAll: () => void ctx.dismissAll(),\n isNew: ctx.isNew,\n markAllSeen,\n getByCategory,\n }),\n [ctx.manifest, ctx.newFeatures, ctx.newCount, ctx.newFeaturesSorted, ctx.dismiss, ctx.dismissAll, ctx.isNew, markAllSeen, getByCategory],\n );\n}\n"]}
|
package/dist/react.cjs
CHANGED
|
@@ -1100,6 +1100,7 @@ function FeatureDropProvider({
|
|
|
1100
1100
|
locale = "en",
|
|
1101
1101
|
animation = "normal",
|
|
1102
1102
|
translations: translationOverrides,
|
|
1103
|
+
engine,
|
|
1103
1104
|
children
|
|
1104
1105
|
}) {
|
|
1105
1106
|
const analyticsRef = react.useRef(analytics);
|
|
@@ -1156,20 +1157,20 @@ function FeatureDropProvider({
|
|
|
1156
1157
|
seenFeatureIds: readIdSet(SEEN_FEATURES_STORAGE_KEY),
|
|
1157
1158
|
clickedFeatureIds: readIdSet(CLICKED_FEATURES_STORAGE_KEY),
|
|
1158
1159
|
triggerContext: (() => {
|
|
1159
|
-
const
|
|
1160
|
-
if (!
|
|
1161
|
-
|
|
1162
|
-
return
|
|
1160
|
+
const engine2 = triggerEngineRef.current;
|
|
1161
|
+
if (!engine2) return void 0;
|
|
1162
|
+
engine2.setElapsedMs(Date.now() - sessionStartedAtRef.current);
|
|
1163
|
+
return engine2.getContext();
|
|
1163
1164
|
})(),
|
|
1164
1165
|
flagBridge
|
|
1165
1166
|
})
|
|
1166
1167
|
);
|
|
1167
1168
|
const recompute = react.useCallback(() => {
|
|
1168
|
-
const
|
|
1169
|
+
const engine2 = triggerEngineRef.current;
|
|
1169
1170
|
let triggerContext;
|
|
1170
|
-
if (
|
|
1171
|
-
|
|
1172
|
-
triggerContext =
|
|
1171
|
+
if (engine2) {
|
|
1172
|
+
engine2.setElapsedMs(Date.now() - sessionStartedAtRef.current);
|
|
1173
|
+
triggerContext = engine2.getContext();
|
|
1173
1174
|
}
|
|
1174
1175
|
setFeatureState(
|
|
1175
1176
|
computeFeatureState({
|
|
@@ -1206,6 +1207,12 @@ function FeatureDropProvider({
|
|
|
1206
1207
|
react.useEffect(() => {
|
|
1207
1208
|
recompute();
|
|
1208
1209
|
}, [recompute]);
|
|
1210
|
+
react.useEffect(() => {
|
|
1211
|
+
engine?.initialize?.();
|
|
1212
|
+
return () => {
|
|
1213
|
+
engine?.destroy?.();
|
|
1214
|
+
};
|
|
1215
|
+
}, [engine]);
|
|
1209
1216
|
const hasTimeTriggers = react.useMemo(
|
|
1210
1217
|
() => resolvedManifest.some((feature) => feature.trigger?.type === "time"),
|
|
1211
1218
|
[resolvedManifest]
|
|
@@ -1488,7 +1495,8 @@ function FeatureDropProvider({
|
|
|
1488
1495
|
trackUsageEvent,
|
|
1489
1496
|
trackTriggerEvent,
|
|
1490
1497
|
trackMilestone,
|
|
1491
|
-
setTriggerPath
|
|
1498
|
+
setTriggerPath,
|
|
1499
|
+
engine: engine ?? null
|
|
1492
1500
|
}),
|
|
1493
1501
|
[
|
|
1494
1502
|
resolvedManifest,
|
|
@@ -1522,7 +1530,8 @@ function FeatureDropProvider({
|
|
|
1522
1530
|
trackUsageEvent,
|
|
1523
1531
|
trackTriggerEvent,
|
|
1524
1532
|
trackMilestone,
|
|
1525
|
-
setTriggerPath
|
|
1533
|
+
setTriggerPath,
|
|
1534
|
+
engine
|
|
1526
1535
|
]
|
|
1527
1536
|
);
|
|
1528
1537
|
return /* @__PURE__ */ jsxRuntime.jsx(FeatureDropContext.Provider, { value, children });
|