@wireai/activation 0.1.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.
Files changed (142) hide show
  1. package/AGENTS.md +73 -0
  2. package/CHANGELOG.md +572 -0
  3. package/INTEGRATION_PROMPT.md +55 -0
  4. package/LICENSE +21 -0
  5. package/README.md +923 -0
  6. package/dist/CenteredModal-C3qQBHsA.d.mts +38 -0
  7. package/dist/CenteredModal-Cdgns6--.d.ts +38 -0
  8. package/dist/coachmarks/index.d.mts +352 -0
  9. package/dist/coachmarks/index.d.ts +352 -0
  10. package/dist/coachmarks/index.js +944 -0
  11. package/dist/coachmarks/index.js.map +1 -0
  12. package/dist/coachmarks/index.mjs +918 -0
  13. package/dist/coachmarks/index.mjs.map +1 -0
  14. package/dist/decision-CFvGY6nP.d.ts +228 -0
  15. package/dist/decision-Cl8OFYzu.d.mts +228 -0
  16. package/dist/index.d.mts +1390 -0
  17. package/dist/index.d.ts +1390 -0
  18. package/dist/index.js +3260 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/index.mjs +3186 -0
  21. package/dist/index.mjs.map +1 -0
  22. package/dist/questionnaire/index.d.mts +249 -0
  23. package/dist/questionnaire/index.d.ts +249 -0
  24. package/dist/questionnaire/index.js +922 -0
  25. package/dist/questionnaire/index.js.map +1 -0
  26. package/dist/questionnaire/index.mjs +906 -0
  27. package/dist/questionnaire/index.mjs.map +1 -0
  28. package/dist/reviews/index.d.mts +213 -0
  29. package/dist/reviews/index.d.ts +213 -0
  30. package/dist/reviews/index.js +1001 -0
  31. package/dist/reviews/index.js.map +1 -0
  32. package/dist/reviews/index.mjs +971 -0
  33. package/dist/reviews/index.mjs.map +1 -0
  34. package/dist/showcase/index.d.mts +119 -0
  35. package/dist/showcase/index.d.ts +119 -0
  36. package/dist/showcase/index.js +660 -0
  37. package/dist/showcase/index.js.map +1 -0
  38. package/dist/showcase/index.mjs +651 -0
  39. package/dist/showcase/index.mjs.map +1 -0
  40. package/dist/types-BKfpdZzX.d.mts +84 -0
  41. package/dist/types-BKfpdZzX.d.ts +84 -0
  42. package/dist/types-CMuOexw0.d.mts +116 -0
  43. package/dist/types-CMuOexw0.d.ts +116 -0
  44. package/dist/types-GL_hQ0TN.d.mts +46 -0
  45. package/dist/types-GL_hQ0TN.d.ts +46 -0
  46. package/llms.txt +36 -0
  47. package/metro/index.d.ts +32 -0
  48. package/metro/index.js +83 -0
  49. package/package.json +171 -0
  50. package/src/DemoOnboarding.tsx +133 -0
  51. package/src/OnboardingFlow.tsx +504 -0
  52. package/src/WireOnboarding.tsx +248 -0
  53. package/src/analytics/analyticsEvent.ts +69 -0
  54. package/src/analytics/reportClientEvent.ts +119 -0
  55. package/src/analytics/sendPreview.ts +73 -0
  56. package/src/attribution/attribution.ts +46 -0
  57. package/src/cards/ChipSelectCard.tsx +265 -0
  58. package/src/cards/InterstitialCard.tsx +299 -0
  59. package/src/cards/NumberStepperCard.tsx +121 -0
  60. package/src/cards/SelectionCard.tsx +270 -0
  61. package/src/cards/StatusCard.tsx +170 -0
  62. package/src/cards/TextInputCard.tsx +147 -0
  63. package/src/cards/index.ts +23 -0
  64. package/src/coachmarks/CoachmarkOverlayHost.tsx +44 -0
  65. package/src/coachmarks/CoachmarkProvider.tsx +108 -0
  66. package/src/coachmarks/GestureHint.tsx +331 -0
  67. package/src/coachmarks/SpotlightOverlay.tsx +254 -0
  68. package/src/coachmarks/coachmarkAnchorRegistry.ts +70 -0
  69. package/src/coachmarks/coachmarkOverlayStore.ts +63 -0
  70. package/src/coachmarks/index.ts +62 -0
  71. package/src/coachmarks/runtime.ts +103 -0
  72. package/src/coachmarks/selectTourSteps.ts +45 -0
  73. package/src/coachmarks/types.ts +58 -0
  74. package/src/coachmarks/useCoachmarkAnchor.ts +49 -0
  75. package/src/coachmarks/useCoachmarkTour.ts +200 -0
  76. package/src/components/AnimatedSparkle.tsx +101 -0
  77. package/src/components/Button.tsx +129 -0
  78. package/src/components/CardHandoff.tsx +212 -0
  79. package/src/components/CardLayout.tsx +103 -0
  80. package/src/components/CenteredModal.tsx +184 -0
  81. package/src/components/CompletionView.tsx +186 -0
  82. package/src/components/DoneBlock.tsx +37 -0
  83. package/src/components/ErrorBlock.tsx +42 -0
  84. package/src/components/Illustration.tsx +36 -0
  85. package/src/components/LoadingBlock.tsx +182 -0
  86. package/src/components/LoadingScreen.tsx +35 -0
  87. package/src/components/OnboardingScaffold.tsx +105 -0
  88. package/src/components/StepProgress.tsx +85 -0
  89. package/src/components/loaderChrome.ts +28 -0
  90. package/src/config/onboardingFlag.ts +39 -0
  91. package/src/config/wireConfigFromEnv.ts +52 -0
  92. package/src/device/deviceContext.ts +158 -0
  93. package/src/features/WireFeaturesProvider.tsx +81 -0
  94. package/src/features/cache.ts +92 -0
  95. package/src/features/defaults.ts +67 -0
  96. package/src/features/fetchWireFeatures.ts +86 -0
  97. package/src/features/index.ts +39 -0
  98. package/src/features/types.ts +61 -0
  99. package/src/features/useWireFeatures.ts +57 -0
  100. package/src/identity/userIdentity.ts +100 -0
  101. package/src/illustrations/defaultIllustrations.tsx +99 -0
  102. package/src/index.ts +159 -0
  103. package/src/motion/centeredModalMotion.ts +45 -0
  104. package/src/motion/motionSpec.ts +133 -0
  105. package/src/motion/useReducedMotion.ts +43 -0
  106. package/src/questionnaire/QuestionnaireGate.tsx +235 -0
  107. package/src/questionnaire/decision.ts +63 -0
  108. package/src/questionnaire/index.ts +54 -0
  109. package/src/questionnaire/runtime.ts +20 -0
  110. package/src/questionnaire/transport.ts +83 -0
  111. package/src/questionnaire/types.ts +182 -0
  112. package/src/questionnaire/useQuestionnaireGate.ts +159 -0
  113. package/src/reviews/ReviewGate.tsx +316 -0
  114. package/src/reviews/ReviewModal.tsx +13 -0
  115. package/src/reviews/decision.ts +120 -0
  116. package/src/reviews/equality.ts +54 -0
  117. package/src/reviews/expo-store-review.d.ts +16 -0
  118. package/src/reviews/feedbackForm.ts +41 -0
  119. package/src/reviews/index.ts +78 -0
  120. package/src/reviews/presentation.ts +29 -0
  121. package/src/reviews/runtime.ts +45 -0
  122. package/src/reviews/storeReview.ts +62 -0
  123. package/src/reviews/transport.ts +89 -0
  124. package/src/reviews/types.ts +172 -0
  125. package/src/reviews/useReviewGate.ts +137 -0
  126. package/src/session/persistedSession.ts +143 -0
  127. package/src/session-analytics/index.ts +18 -0
  128. package/src/session-analytics/reportSessionStart.ts +144 -0
  129. package/src/session-analytics/useSessionStart.ts +114 -0
  130. package/src/showcase/FeatureShowcase.tsx +233 -0
  131. package/src/showcase/index.ts +19 -0
  132. package/src/showcase/selectShowcaseSlides.ts +45 -0
  133. package/src/showcase/showcaseColors.ts +91 -0
  134. package/src/showcase/types.ts +74 -0
  135. package/src/theme/ThemeContext.tsx +38 -0
  136. package/src/theme/defaultTheme.ts +70 -0
  137. package/src/theme/themeFromBrand.ts +93 -0
  138. package/src/theme/types.ts +88 -0
  139. package/src/theme/typography.ts +46 -0
  140. package/src/types.ts +215 -0
  141. package/src/utils/deriveAnswers.ts +43 -0
  142. package/src/utils/readProgress.ts +33 -0
@@ -0,0 +1,651 @@
1
+ import Onboarding from '@blazejkustra/react-native-onboarding';
2
+ import React, { createContext, useEffect, useMemo, useRef, useCallback, useState, useContext } from 'react';
3
+ import { StyleSheet, View } from 'react-native';
4
+ import Animated, { useSharedValue, useReducedMotion, withRepeat, withSequence, withTiming, withDelay, useAnimatedStyle } from 'react-native-reanimated';
5
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
+
7
+ // src/showcase/FeatureShowcase.tsx
8
+ var SWIPE_MS = 700;
9
+ var TAP_MS = 320;
10
+ var SPREAD_MS = 620;
11
+ var GLYPH_ROTATION = {
12
+ swipe_up: "0deg",
13
+ swipe_down: "180deg",
14
+ swipe_left: "-90deg",
15
+ swipe_right: "90deg"
16
+ };
17
+ var FingerPair = ({ size, color }) => {
18
+ const { row, index, middle } = useMemo(() => {
19
+ const w = size * 0.2;
20
+ const r = w / 2;
21
+ return {
22
+ row: { flexDirection: "row", alignItems: "flex-end", gap: size * 0.08 },
23
+ index: { width: w, height: size * 0.54, borderRadius: r, backgroundColor: color },
24
+ middle: { width: w, height: size * 0.44, borderRadius: r, backgroundColor: color }
25
+ };
26
+ }, [size, color]);
27
+ return /* @__PURE__ */ jsxs(View, { style: row, pointerEvents: "none", children: [
28
+ /* @__PURE__ */ jsx(View, { style: index }),
29
+ /* @__PURE__ */ jsx(View, { style: middle })
30
+ ] });
31
+ };
32
+ var MotionTrack = ({ size, color }) => {
33
+ const { wrap, tick0, tick1 } = useMemo(() => {
34
+ const h = Math.max(2, size * 0.05);
35
+ return {
36
+ wrap: { alignItems: "center", marginTop: size * 0.1, gap: size * 0.06 },
37
+ tick0: {
38
+ width: size * 0.3,
39
+ height: h,
40
+ borderRadius: h / 2,
41
+ backgroundColor: color,
42
+ opacity: 0.4
43
+ },
44
+ tick1: {
45
+ width: size * 0.18,
46
+ height: h,
47
+ borderRadius: h / 2,
48
+ backgroundColor: color,
49
+ opacity: 0.16
50
+ }
51
+ };
52
+ }, [size, color]);
53
+ return /* @__PURE__ */ jsxs(View, { style: wrap, pointerEvents: "none", children: [
54
+ /* @__PURE__ */ jsx(View, { style: tick0 }),
55
+ /* @__PURE__ */ jsx(View, { style: tick1 })
56
+ ] });
57
+ };
58
+ var GestureHintComponent = ({
59
+ type,
60
+ size = 34,
61
+ color = "white",
62
+ renderHand
63
+ }) => {
64
+ var _a;
65
+ const translate = useSharedValue(0);
66
+ const opacity = useSharedValue(1);
67
+ const pulse = useSharedValue(1);
68
+ const spread = useSharedValue(0);
69
+ const reduced = useReducedMotion();
70
+ const isSwipe = type === "swipe_up" || type === "swipe_down" || type === "swipe_left" || type === "swipe_right";
71
+ const isSpread = type === "pinch" || type === "zoom";
72
+ const isTap = type === "tap" || type === "double_tap";
73
+ useEffect(() => {
74
+ if (type === "none" || reduced) return void 0;
75
+ if (isTap) {
76
+ pulse.value = withRepeat(
77
+ withSequence(
78
+ withTiming(0.82, { duration: TAP_MS }),
79
+ withTiming(1, { duration: TAP_MS })
80
+ ),
81
+ -1,
82
+ false
83
+ );
84
+ opacity.value = withRepeat(
85
+ withSequence(
86
+ withTiming(0.55, { duration: TAP_MS }),
87
+ withTiming(1, { duration: TAP_MS })
88
+ ),
89
+ -1,
90
+ false
91
+ );
92
+ return void 0;
93
+ }
94
+ if (isSpread) {
95
+ spread.value = withRepeat(
96
+ withSequence(
97
+ withTiming(size * 0.26, { duration: SPREAD_MS }),
98
+ withTiming(0, { duration: SPREAD_MS })
99
+ ),
100
+ -1,
101
+ false
102
+ );
103
+ return void 0;
104
+ }
105
+ translate.value = withRepeat(
106
+ withSequence(
107
+ withTiming(0, { duration: 0 }),
108
+ withTiming(-26, { duration: SWIPE_MS }),
109
+ withDelay(140, withTiming(0, { duration: 0 }))
110
+ ),
111
+ -1,
112
+ false
113
+ );
114
+ opacity.value = withRepeat(
115
+ withSequence(
116
+ withTiming(1, { duration: SWIPE_MS / 2 }),
117
+ withTiming(0, { duration: SWIPE_MS / 2 }),
118
+ withDelay(140, withTiming(1, { duration: 0 }))
119
+ ),
120
+ -1,
121
+ false
122
+ );
123
+ return void 0;
124
+ }, [type, reduced, isTap, isSpread, translate, opacity, pulse, spread, size]);
125
+ const swipeAnimatedStyle = useAnimatedStyle(() => ({
126
+ opacity: opacity.value,
127
+ transform: [{ translateY: translate.value }]
128
+ }));
129
+ const tapAnimatedStyle = useAnimatedStyle(() => ({
130
+ opacity: opacity.value,
131
+ transform: [{ scale: pulse.value }]
132
+ }));
133
+ const spreadLeftStyle = useAnimatedStyle(() => ({
134
+ transform: [{ translateX: -spread.value }]
135
+ }));
136
+ const spreadRightStyle = useAnimatedStyle(() => ({
137
+ transform: [{ translateX: spread.value }]
138
+ }));
139
+ if (type === "none") return null;
140
+ const customGlyph = renderHand ? renderHand({ size, color }) : null;
141
+ if (isSwipe) {
142
+ return /* @__PURE__ */ jsx(
143
+ View,
144
+ {
145
+ style: { transform: [{ rotate: (_a = GLYPH_ROTATION[type]) != null ? _a : "0deg" }] },
146
+ pointerEvents: "none",
147
+ children: /* @__PURE__ */ jsxs(View, { style: styles.swipeStack, children: [
148
+ /* @__PURE__ */ jsx(Animated.View, { style: swipeAnimatedStyle, pointerEvents: "none", children: customGlyph != null ? customGlyph : /* @__PURE__ */ jsx(FingerPair, { size, color }) }),
149
+ /* @__PURE__ */ jsx(MotionTrack, { size, color })
150
+ ] })
151
+ }
152
+ );
153
+ }
154
+ if (isSpread) {
155
+ const w = size * 0.2;
156
+ const capsule = {
157
+ width: w,
158
+ height: size * 0.5,
159
+ borderRadius: w / 2,
160
+ backgroundColor: color
161
+ };
162
+ return /* @__PURE__ */ jsxs(View, { style: [styles.spreadRow, { gap: size * 0.12 }], pointerEvents: "none", children: [
163
+ /* @__PURE__ */ jsx(Animated.View, { style: spreadLeftStyle, children: /* @__PURE__ */ jsx(View, { style: capsule }) }),
164
+ /* @__PURE__ */ jsx(Animated.View, { style: spreadRightStyle, children: /* @__PURE__ */ jsx(View, { style: capsule }) })
165
+ ] });
166
+ }
167
+ const ringSize = size * 0.86;
168
+ const ringBorder = Math.max(2, size * 0.08);
169
+ const dotSize = size * 0.32;
170
+ return /* @__PURE__ */ jsx(View, { style: [styles.tapWrap, { width: size, height: size }], pointerEvents: "none", children: customGlyph ? /* @__PURE__ */ jsx(Animated.View, { style: tapAnimatedStyle, pointerEvents: "none", children: customGlyph }) : /* @__PURE__ */ jsxs(Fragment, { children: [
171
+ /* @__PURE__ */ jsx(
172
+ Animated.View,
173
+ {
174
+ style: [
175
+ styles.tapRing,
176
+ {
177
+ width: ringSize,
178
+ height: ringSize,
179
+ borderRadius: ringSize / 2,
180
+ borderWidth: ringBorder,
181
+ borderColor: color
182
+ },
183
+ tapAnimatedStyle
184
+ ],
185
+ pointerEvents: "none"
186
+ }
187
+ ),
188
+ /* @__PURE__ */ jsx(
189
+ View,
190
+ {
191
+ style: {
192
+ position: "absolute",
193
+ width: dotSize,
194
+ height: dotSize,
195
+ borderRadius: dotSize / 2,
196
+ backgroundColor: color
197
+ }
198
+ }
199
+ )
200
+ ] }) });
201
+ };
202
+ var styles = StyleSheet.create({
203
+ swipeStack: {
204
+ alignItems: "center",
205
+ justifyContent: "center"
206
+ },
207
+ spreadRow: {
208
+ flexDirection: "row",
209
+ alignItems: "center",
210
+ justifyContent: "center"
211
+ },
212
+ tapWrap: {
213
+ alignItems: "center",
214
+ justifyContent: "center"
215
+ },
216
+ tapRing: {
217
+ backgroundColor: "transparent"
218
+ }
219
+ });
220
+ var GestureHint = React.memo(GestureHintComponent);
221
+ GestureHint.displayName = "GestureHint";
222
+
223
+ // src/coachmarks/runtime.ts
224
+ var storageSingleton = null;
225
+ var testing = false;
226
+ var showcaseGateKey = (showcaseId) => `wire_showcase_${showcaseId}_seen`;
227
+ var SEEN_VALUE = "1";
228
+ var hasSeenGate = (key, storageOverride, testingOverride) => {
229
+ if (testingOverride != null ? testingOverride : testing) return false;
230
+ const storage = storageOverride != null ? storageOverride : storageSingleton;
231
+ if (!storage) return false;
232
+ try {
233
+ return storage.getItem(key) === SEEN_VALUE;
234
+ } catch {
235
+ return false;
236
+ }
237
+ };
238
+ var markSeenGate = (key, storageOverride, testingOverride) => {
239
+ if (testingOverride != null ? testingOverride : testing) return;
240
+ const storage = storageOverride != null ? storageOverride : storageSingleton;
241
+ if (!storage) return;
242
+ try {
243
+ storage.setItem(key, SEEN_VALUE);
244
+ } catch {
245
+ }
246
+ };
247
+
248
+ // src/features/defaults.ts
249
+ var defaultWireFeatures = Object.freeze({
250
+ coachmarks: Object.freeze({ enabled: true }),
251
+ showcase: Object.freeze({ enabled: true }),
252
+ onboarding: Object.freeze({ mode: "ai" }),
253
+ review: Object.freeze({ enabled: true }),
254
+ questionnaire: Object.freeze({ enabled: true }),
255
+ announcements: Object.freeze({ enabled: true })
256
+ });
257
+ var VALID_MODES = ["ai", "static", "off"];
258
+ var readEnabled = (raw) => {
259
+ if (raw && typeof raw === "object" && typeof raw.enabled === "boolean") {
260
+ return { enabled: raw.enabled };
261
+ }
262
+ return { enabled: true };
263
+ };
264
+ var parseWireFeatures = (raw) => {
265
+ var _a;
266
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return defaultWireFeatures;
267
+ const src = raw;
268
+ const modeRaw = (_a = src.onboarding) == null ? void 0 : _a.mode;
269
+ const mode = typeof modeRaw === "string" && VALID_MODES.includes(modeRaw) ? modeRaw : "ai";
270
+ return {
271
+ coachmarks: readEnabled(src.coachmarks),
272
+ showcase: readEnabled(src.showcase),
273
+ onboarding: { mode },
274
+ review: readEnabled(src.review),
275
+ questionnaire: readEnabled(src.questionnaire),
276
+ announcements: readEnabled(src.announcements)
277
+ };
278
+ };
279
+ var featuresEqual = (a, b) => a === b || a.coachmarks.enabled === b.coachmarks.enabled && a.showcase.enabled === b.showcase.enabled && a.onboarding.mode === b.onboarding.mode && a.review.enabled === b.review.enabled && a.questionnaire.enabled === b.questionnaire.enabled && a.announcements.enabled === b.announcements.enabled;
280
+
281
+ // src/features/cache.ts
282
+ var READ_TIMEOUT_MS = 1500;
283
+ var featuresCacheKey = (appId) => `wireai:features:${appId && appId.length > 0 ? appId : "default"}`;
284
+ var withTimeout = (p, ms) => {
285
+ let timer;
286
+ const timeout = new Promise((resolve) => {
287
+ timer = setTimeout(() => resolve(void 0), ms);
288
+ });
289
+ return Promise.race([p, timeout]).finally(() => clearTimeout(timer));
290
+ };
291
+ var readCachedFeatures = async (storage, key) => {
292
+ let raw;
293
+ try {
294
+ raw = await withTimeout(storage.getItem(key), READ_TIMEOUT_MS);
295
+ } catch {
296
+ return void 0;
297
+ }
298
+ if (!raw) return void 0;
299
+ try {
300
+ const parsed = JSON.parse(raw);
301
+ if (parsed && typeof parsed === "object" && typeof parsed.ts === "number") {
302
+ const ts = parsed.ts;
303
+ return {
304
+ features: parseWireFeatures(parsed.features),
305
+ ageMs: Date.now() - ts
306
+ };
307
+ }
308
+ } catch {
309
+ }
310
+ return void 0;
311
+ };
312
+ var writeCachedFeatures = (storage, key, features) => {
313
+ try {
314
+ void storage.setItem(key, JSON.stringify({ features, ts: Date.now() })).catch(() => {
315
+ });
316
+ } catch {
317
+ }
318
+ };
319
+
320
+ // src/features/fetchWireFeatures.ts
321
+ var DEFAULT_TIMEOUT_MS = 4e3;
322
+ var failOpen = async (storage, key) => {
323
+ var _a;
324
+ if (!storage) return defaultWireFeatures;
325
+ const cached = await readCachedFeatures(storage, key);
326
+ return (_a = cached == null ? void 0 : cached.features) != null ? _a : defaultWireFeatures;
327
+ };
328
+ var fetchWithTimeout = async (url, apiKey, timeoutMs) => {
329
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
330
+ const timer = setTimeout(() => controller == null ? void 0 : controller.abort(), timeoutMs);
331
+ try {
332
+ return await fetch(url, {
333
+ method: "GET",
334
+ headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
335
+ signal: controller == null ? void 0 : controller.signal
336
+ });
337
+ } finally {
338
+ clearTimeout(timer);
339
+ }
340
+ };
341
+ var fetchWireFeatures = async (config) => {
342
+ const key = featuresCacheKey(config == null ? void 0 : config.appId);
343
+ const storage = config == null ? void 0 : config.storage;
344
+ if (!(config == null ? void 0 : config.serverUrl) || !(config == null ? void 0 : config.apiKey)) return failOpen(storage, key);
345
+ const url = `${config.serverUrl.replace(/\/$/, "")}/v1/features`;
346
+ const timeoutMs = config.timeoutMs && config.timeoutMs > 0 ? config.timeoutMs : DEFAULT_TIMEOUT_MS;
347
+ try {
348
+ const res = await fetchWithTimeout(url, config.apiKey, timeoutMs);
349
+ if (!res || !res.ok) return failOpen(storage, key);
350
+ const json = await res.json();
351
+ const features = parseWireFeatures(json);
352
+ if (storage) writeCachedFeatures(storage, key, features);
353
+ return features;
354
+ } catch {
355
+ return failOpen(storage, key);
356
+ }
357
+ };
358
+
359
+ // src/features/useWireFeatures.ts
360
+ var useStableValue = (value, isEqual) => {
361
+ const ref = useRef(value);
362
+ if (!isEqual(ref.current, value)) ref.current = value;
363
+ return ref.current;
364
+ };
365
+ var sameFetchKey = (a, b) => (a == null ? void 0 : a.serverUrl) === (b == null ? void 0 : b.serverUrl) && (a == null ? void 0 : a.apiKey) === (b == null ? void 0 : b.apiKey) && (a == null ? void 0 : a.appId) === (b == null ? void 0 : b.appId);
366
+ var useWireFeatures = (config) => {
367
+ const stableConfig = useStableValue(config, sameFetchKey);
368
+ const [flags, setFlags] = useState(defaultWireFeatures);
369
+ const canFetch = !!(stableConfig == null ? void 0 : stableConfig.serverUrl) && !!(stableConfig == null ? void 0 : stableConfig.apiKey);
370
+ useEffect(() => {
371
+ if (!canFetch) {
372
+ setFlags((prev) => featuresEqual(prev, defaultWireFeatures) ? prev : defaultWireFeatures);
373
+ return;
374
+ }
375
+ let alive = true;
376
+ void fetchWireFeatures(stableConfig).then((next) => {
377
+ if (!alive) return;
378
+ setFlags((prev) => featuresEqual(prev, next) ? prev : next);
379
+ });
380
+ return () => {
381
+ alive = false;
382
+ };
383
+ }, [canFetch, stableConfig]);
384
+ return flags;
385
+ };
386
+ var WireFeaturesContext = createContext(null);
387
+ var useWireFeaturesContext = () => useContext(WireFeaturesContext);
388
+ var useResolvedFeatures = (options) => {
389
+ var _a, _b, _c;
390
+ const ctx = useWireFeaturesContext();
391
+ const shouldFetch = !(options == null ? void 0 : options.flags) && ctx == null;
392
+ const fetched = useWireFeatures(shouldFetch ? options == null ? void 0 : options.config : void 0);
393
+ return (_c = (_b = (_a = options == null ? void 0 : options.flags) != null ? _a : ctx) != null ? _b : fetched) != null ? _c : defaultWireFeatures;
394
+ };
395
+
396
+ // src/theme/defaultTheme.ts
397
+ var defaultOnboardingTheme = {
398
+ colors: {
399
+ background: "#FFFFFF",
400
+ surface: "#F7F7F8",
401
+ text: "#101114",
402
+ textMuted: "#6B7280",
403
+ primary: "#111111",
404
+ onPrimary: "#FFFFFF",
405
+ primarySoft: "rgba(17,17,17,0.06)",
406
+ border: "#E4E4E7",
407
+ borderStrong: "#C7C7CC",
408
+ disabled: "#EBEBEB",
409
+ success: "#16A34A",
410
+ warning: "#D97706",
411
+ error: "#DC2626",
412
+ info: "#2563EB"
413
+ },
414
+ fonts: {
415
+ regular: void 0,
416
+ medium: void 0,
417
+ bold: void 0,
418
+ baseSize: 16,
419
+ scale: 1
420
+ },
421
+ radius: {
422
+ sm: 8,
423
+ md: 12,
424
+ lg: 16,
425
+ full: 9999
426
+ },
427
+ spacing: {
428
+ xs: 4,
429
+ sm: 8,
430
+ md: 16,
431
+ lg: 24,
432
+ xl: 32
433
+ },
434
+ // Circled (pill) CTA by default; an app can pass `button: { radius: 12 }` for
435
+ // a rounded rectangle, or override height/paddingX to match its design system.
436
+ button: {
437
+ radius: 9999,
438
+ height: 52,
439
+ paddingX: 24
440
+ }
441
+ };
442
+ var mergeTheme = (override) => {
443
+ return defaultOnboardingTheme;
444
+ };
445
+ var OnboardingThemeContext = createContext(null);
446
+ var useOnboardingTheme = () => {
447
+ const ctx = useContext(OnboardingThemeContext);
448
+ return ctx != null ? ctx : mergeTheme();
449
+ };
450
+
451
+ // src/showcase/showcaseColors.ts
452
+ var showcaseColorsFromTheme = (t, accent) => ({
453
+ background: {
454
+ // Brand action color → primary button bg + intro highlight + image hero panel.
455
+ // (The hero panel is overridden to a calm surface via the package's
456
+ // `background` render prop — see showcasePanelBackground — so in practice
457
+ // this only paints the primary button + the intro highlight word.)
458
+ primary: accent,
459
+ // Neutral screen / modal / step-panel background.
460
+ secondary: t.colors.background,
461
+ // Step-label chip background (a soft wash of the brand).
462
+ label: t.colors.primarySoft,
463
+ // Secondary (outline) button border.
464
+ accent
465
+ },
466
+ text: {
467
+ primary: t.colors.text,
468
+ secondary: t.colors.textMuted,
469
+ // Primary button label — sits on `background.primary` (the accent).
470
+ contrast: t.colors.onPrimary
471
+ }
472
+ });
473
+ var SHOWCASE_PANEL_TINT_OPACITY = 0.1;
474
+ var showcasePanelBackground = (t, accent) => ({
475
+ base: t.colors.surface,
476
+ tint: { color: accent, opacity: SHOWCASE_PANEL_TINT_OPACITY }
477
+ });
478
+ var TRANSPARENT_PX = {
479
+ uri: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
480
+ };
481
+ var mergeThemeOver = (base, override) => {
482
+ if (!override) return base;
483
+ return {
484
+ colors: { ...base.colors, ...override.colors },
485
+ fonts: { ...base.fonts, ...override.fonts },
486
+ radius: { ...base.radius, ...override.radius },
487
+ spacing: { ...base.spacing, ...override.spacing },
488
+ button: { ...base.button, ...override.button }
489
+ };
490
+ };
491
+ var FeatureShowcase = ({
492
+ config,
493
+ onDone,
494
+ theme: themeOverride,
495
+ accentColor,
496
+ buttonLabel = "Next",
497
+ storage,
498
+ isTesting,
499
+ features,
500
+ featuresConfig
501
+ }) => {
502
+ var _a;
503
+ const active = useOnboardingTheme();
504
+ const t = useMemo(
505
+ () => mergeThemeOver(active, themeOverride),
506
+ [active, themeOverride]
507
+ );
508
+ const accent = accentColor != null ? accentColor : t.colors.primary;
509
+ const flags = useResolvedFeatures({ flags: features, config: featuresConfig });
510
+ const disabled = !flags.showcase.enabled;
511
+ const gateKey = showcaseGateKey(config.id);
512
+ const seen = useMemo(
513
+ () => hasSeenGate(gateKey, storage, isTesting),
514
+ [gateKey, storage, isTesting]
515
+ );
516
+ const skip = seen || disabled;
517
+ const doneRef = useRef(false);
518
+ const finish = useCallback(() => {
519
+ if (doneRef.current) return;
520
+ doneRef.current = true;
521
+ markSeenGate(gateKey, storage, isTesting);
522
+ onDone();
523
+ }, [gateKey, storage, isTesting, onDone]);
524
+ useEffect(() => {
525
+ if (skip && !doneRef.current) {
526
+ doneRef.current = true;
527
+ onDone();
528
+ }
529
+ }, [skip, onDone]);
530
+ const [activeIndex, setActiveIndex] = useState(0);
531
+ const steps = useMemo(
532
+ () => config.slides.map((slide) => {
533
+ var _a2, _b, _c;
534
+ return {
535
+ title: slide.title,
536
+ description: slide.description,
537
+ buttonLabel: (_a2 = slide.buttonLabel) != null ? _a2 : buttonLabel,
538
+ image: (_b = slide.image) != null ? _b : TRANSPARENT_PX,
539
+ position: (_c = slide.imagePosition) != null ? _c : "top"
540
+ };
541
+ }),
542
+ [config.slides, buttonLabel]
543
+ );
544
+ const introPanel = useMemo(
545
+ () => config.introPanel ? {
546
+ title: config.introPanel.title,
547
+ subtitle: config.introPanel.subtitle,
548
+ button: config.introPanel.button,
549
+ image: config.introPanel.image
550
+ } : { button: buttonLabel },
551
+ [config.introPanel, buttonLabel]
552
+ );
553
+ const colors = useMemo(
554
+ () => showcaseColorsFromTheme(t, accent),
555
+ [t, accent]
556
+ );
557
+ const panelBg = useMemo(() => showcasePanelBackground(t, accent), [t, accent]);
558
+ const renderPanelBackground = useCallback(
559
+ () => /* @__PURE__ */ jsx(
560
+ View,
561
+ {
562
+ style: [
563
+ styles2.panelBase,
564
+ { backgroundColor: panelBg.base, borderRadius: t.radius.md }
565
+ ],
566
+ children: /* @__PURE__ */ jsx(
567
+ View,
568
+ {
569
+ style: [
570
+ StyleSheet.absoluteFill,
571
+ {
572
+ backgroundColor: panelBg.tint.color,
573
+ opacity: panelBg.tint.opacity
574
+ }
575
+ ]
576
+ }
577
+ )
578
+ }
579
+ ),
580
+ [panelBg, t.radius.md]
581
+ );
582
+ const fonts = useMemo(
583
+ () => ({
584
+ introTitle: t.fonts.bold,
585
+ introSubtitle: t.fonts.regular,
586
+ introButton: t.fonts.bold,
587
+ stepLabel: t.fonts.medium,
588
+ stepTitle: t.fonts.bold,
589
+ stepDescription: t.fonts.regular,
590
+ stepButton: t.fonts.bold,
591
+ primaryButton: t.fonts.bold,
592
+ secondaryButton: t.fonts.medium
593
+ }),
594
+ [t]
595
+ );
596
+ if (skip) return null;
597
+ const activeGesture = (_a = config.slides[activeIndex]) == null ? void 0 : _a.gesture;
598
+ const showGesture = !!activeGesture && activeGesture !== "none" && activeGesture !== "tap" && activeGesture !== "double_tap";
599
+ return /* @__PURE__ */ jsxs(View, { style: styles2.fill, children: [
600
+ /* @__PURE__ */ jsx(
601
+ Onboarding,
602
+ {
603
+ introPanel,
604
+ steps,
605
+ colors,
606
+ fonts,
607
+ background: renderPanelBackground,
608
+ onComplete: finish,
609
+ onSkip: finish,
610
+ onStepChange: setActiveIndex
611
+ }
612
+ ),
613
+ showGesture && /* @__PURE__ */ jsx(View, { style: styles2.gestureLayer, pointerEvents: "none", children: /* @__PURE__ */ jsx(GestureHint, { type: activeGesture, color: accent }) })
614
+ ] });
615
+ };
616
+ FeatureShowcase.displayName = "FeatureShowcase";
617
+ var styles2 = StyleSheet.create({
618
+ fill: { flex: 1 },
619
+ // Fills the package's animated background box; clips the accent tint to the
620
+ // rounded corners so the calm panel reads as a card, not a bleed.
621
+ panelBase: { flex: 1, overflow: "hidden" },
622
+ gestureLayer: {
623
+ position: "absolute",
624
+ top: 0,
625
+ left: 0,
626
+ right: 0,
627
+ bottom: 0,
628
+ alignItems: "center",
629
+ justifyContent: "flex-end",
630
+ paddingBottom: 140
631
+ }
632
+ });
633
+
634
+ // src/showcase/selectShowcaseSlides.ts
635
+ var selectShowcaseSlides = (catalog, selection) => {
636
+ if (!selection) return catalog;
637
+ const byId = new Map(catalog.map((item) => [item.id, item]));
638
+ const seen = /* @__PURE__ */ new Set();
639
+ const ordered = [];
640
+ for (const id of selection) {
641
+ if (seen.has(id)) continue;
642
+ seen.add(id);
643
+ const item = byId.get(id);
644
+ if (item) ordered.push(item);
645
+ }
646
+ return ordered;
647
+ };
648
+
649
+ export { FeatureShowcase, selectShowcaseSlides };
650
+ //# sourceMappingURL=index.mjs.map
651
+ //# sourceMappingURL=index.mjs.map