@wireai/activation 0.14.1 → 0.14.3
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/CHANGELOG.md +97 -0
- package/README.md +12 -2
- package/dist/coachmarks/index.d.mts +12 -4
- package/dist/coachmarks/index.d.ts +12 -4
- package/dist/coachmarks/index.js +270 -58
- package/dist/coachmarks/index.js.map +1 -1
- package/dist/coachmarks/index.mjs +224 -11
- package/dist/coachmarks/index.mjs.map +1 -1
- package/dist/showcase/index.js +235 -46
- package/dist/showcase/index.js.map +1 -1
- package/dist/showcase/index.mjs +201 -10
- package/dist/showcase/index.mjs.map +1 -1
- package/metro/index.js +18 -5
- package/package.json +1 -1
- package/src/coachmarks/GestureHint.tsx +16 -7
- package/src/coachmarks/SpotlightOverlay.tsx +51 -18
- package/src/coachmarks/expoBlur.ts +135 -0
- package/src/coachmarks/index.ts +7 -2
- package/src/coachmarks/reanimated.ts +342 -0
- package/src/showcase/FeatureShowcase.tsx +25 -9
- package/src/showcase/blazejOnboarding.ts +136 -0
- package/src/showcase/index.ts +8 -4
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,103 @@
|
|
|
3
3
|
All notable changes to `@wireai/activation` (formerly `wireai-onboarding`).
|
|
4
4
|
Historical entries below the rename keep the old package name on purpose.
|
|
5
5
|
|
|
6
|
+
## [0.14.3] - 2026-08-18
|
|
7
|
+
|
|
8
|
+
**PATCH: no public surface gained, lost or renamed a member.** `SpotlightOverlay`, `GestureHint`
|
|
9
|
+
and `FeatureShowcase` keep their props and their exports; `react-native-reanimated` and
|
|
10
|
+
`@blazejkustra/react-native-onboarding` stay OPTIONAL peers. This finishes the class 0.14.2 started
|
|
11
|
+
with `expo-blur`: **no source under `src/` statically value-imports an optional peer any more.**
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **The coachmarks subpath no longer force-imports its optional peer `react-native-reanimated`.**
|
|
16
|
+
`SpotlightOverlay` and `GestureHint` both did `import Animated, { … } from
|
|
17
|
+
"react-native-reanimated"` at module scope, and `coachmarks/index.ts` re-exports them with no
|
|
18
|
+
wildcard escape — so importing ANYTHING from `@wireai/activation/coachmarks`, even
|
|
19
|
+
`setCoachmarkStorage`, dragged the peer in, and `@wireai/activation/showcase` inherited the same
|
|
20
|
+
edge through `GestureHint`. A host that took "optional" at its word hit a Metro resolution
|
|
21
|
+
failure. The peer is now resolved through a guarded lazy require with a string-literal specifier
|
|
22
|
+
inside a try/catch (`src/coachmarks/reanimated.ts`, the same shape as `icons/expoIcons.ts` /
|
|
23
|
+
`coachmarks/expoBlur.ts`), so neither subpath carries a static top-level import of it.
|
|
24
|
+
- **Degradation, by design:** without the peer both surfaces render STATICALLY at their resting
|
|
25
|
+
frame. The spotlight keeps its ring, tooltip, copy, scrim/frost and gestures, with no fade-in
|
|
26
|
+
and no pulse; the gesture glyph keeps its capsules, ring, dot and motion track, at full opacity
|
|
27
|
+
and no travel. Nothing moves; everything renders. Reduce Motion is still honoured — the kit's
|
|
28
|
+
own `AccessibilityInfo`-backed hook stands in for the peer's `useReducedMotion`.
|
|
29
|
+
- The accessor re-exports the peer's own names (`useAnimatedStyle`, `useAnimatedProps`,
|
|
30
|
+
`withTiming`, …) and the call sites keep calling them unqualified, because reanimated's Babel
|
|
31
|
+
plugin decides what to workletize by the callee NAME. Hosts that HAVE the peer therefore get
|
|
32
|
+
byte-for-byte the same worklets, on the UI thread, as before.
|
|
33
|
+
|
|
34
|
+
- **The showcase subpath no longer force-imports its optional peer
|
|
35
|
+
`@blazejkustra/react-native-onboarding`.** `FeatureShowcase` imported the pager's default export
|
|
36
|
+
at module scope, so importing anything from `@wireai/activation/showcase` — even
|
|
37
|
+
`selectShowcaseSlides` — required a peer npm never installs. It is now resolved through a guarded
|
|
38
|
+
lazy require (`src/showcase/blazejOnboarding.ts`); only the TYPES are still imported statically,
|
|
39
|
+
which every toolchain erases.
|
|
40
|
+
- **Degradation, by design:** absent, the showcase renders null and calls `onDone` from an effect
|
|
41
|
+
— the SAME path the feature kill switch already takes — so the host's flow always advances
|
|
42
|
+
rather than stranding the user on a blank screen. The seen gate is deliberately NOT written, so
|
|
43
|
+
installing the peer later still plays the deck once.
|
|
44
|
+
|
|
45
|
+
- **A rule file prescribed the inverted optional-require contract, for the second time.**
|
|
46
|
+
`ai_rules/context_map.md` told the next author that the `expoIcons` callee "MUST be a local `req`,
|
|
47
|
+
never a literal `require`" — verbatim the `const req = require; req(name)` shape that
|
|
48
|
+
`icons/expoIcons.ts` autopsies as the 0.8.0 red-box bug (aliasing hides the call from Metro's
|
|
49
|
+
static collector, so the module never enters the bundle). 0.14.2 corrected this claim in
|
|
50
|
+
`frequent_rules.md`; this was its second copy, and it was still live. Rewritten to the one correct
|
|
51
|
+
contract, citing `src/icons/expoIcons.ts:98`.
|
|
52
|
+
|
|
53
|
+
### Changed
|
|
54
|
+
|
|
55
|
+
- **`metro/index.js` `OPTIONAL_MODULES` now lists both modules**, and `tsup.config.ts` drops them
|
|
56
|
+
from its static-peer list (`external` is derived from the union, so they stay external, and the
|
|
57
|
+
CJS keep-literal-require hook now covers them). The helper's "a statically-imported module must NOT be
|
|
58
|
+
stubbed" note named exactly these two; it is rewritten, because the kit now has NO
|
|
59
|
+
statically-imported optional peer — everything it imports statically is a REQUIRED peer whose
|
|
60
|
+
absence must stay a hard build error.
|
|
61
|
+
- **Docs that promised the old behaviour are corrected in the same wave**, both directions: the
|
|
62
|
+
coachmarks and showcase barrel docs and the README said importing a subpath "pulls in" these
|
|
63
|
+
peers (no longer true), while the README/AGENTS peer tables already claimed every optional peer
|
|
64
|
+
"sits behind a guarded lazy require" (not true until now).
|
|
65
|
+
|
|
66
|
+
### Added
|
|
67
|
+
|
|
68
|
+
- **A whole-class ratchet in `test/canary/metroResolution.test.tsx`:** every file under `src/` is
|
|
69
|
+
scanned for a static value-import of ANY module in `OPTIONAL_MODULES`, with a positive control
|
|
70
|
+
proving the scanner catches default, named, wrapped multi-line, bare and subpath imports while
|
|
71
|
+
ignoring `import type` and the guarded require itself. Each optional peer so far has arrived as
|
|
72
|
+
the same defect; a new one now inherits the check for free. Plus per-module collection tests
|
|
73
|
+
(Metro marks both new specifiers `isOptional`) and component-level canaries for both peers in
|
|
74
|
+
both directions, including the absent-reanimated/present-blur combination that pins the frost at
|
|
75
|
+
full intensity.
|
|
76
|
+
|
|
77
|
+
## [0.14.2] - 2026-08-18
|
|
78
|
+
|
|
79
|
+
**PATCH: no public surface gained, lost or renamed a member.** `SpotlightOverlay` keeps its props
|
|
80
|
+
and its export; `expo-blur` stays an OPTIONAL peer.
|
|
81
|
+
|
|
82
|
+
### Fixed
|
|
83
|
+
|
|
84
|
+
- **The coachmarks subpath no longer force-imports its optional peer `expo-blur`.**
|
|
85
|
+
`SpotlightOverlay` did `import { BlurView } from "expo-blur"` at module scope and built its
|
|
86
|
+
animated blur component at module load, and `coachmarks/index.ts` re-exports the overlay with no
|
|
87
|
+
wildcard escape — so importing ANYTHING from `@wireai/activation/coachmarks`, even
|
|
88
|
+
`setCoachmarkStorage`, dragged `expo-blur` in. A host that took the peer's "optional" at its word
|
|
89
|
+
and skipped installing it hit a Metro resolution failure the moment it touched coachmarks, which
|
|
90
|
+
is why the AI Mobile Launcher tiers (and any Expo Go build) could not adopt the capability. The
|
|
91
|
+
peer is now resolved through a guarded lazy require with a string-literal specifier inside a
|
|
92
|
+
try/catch (the same shape as `icons/expoIcons.ts` / `device/appVersion.ts`), and the animated
|
|
93
|
+
component is built once on first render, so the subpath carries no static top-level import of the
|
|
94
|
+
peer.
|
|
95
|
+
- **Degradation, by design:** when `expo-blur` is absent the spotlight drops the frost to an
|
|
96
|
+
equivalent dimmed scrim. The ring, tooltip and gestures behave identically — a missing
|
|
97
|
+
decorative frost never blanks a tour, never crashes, never renders empty.
|
|
98
|
+
- `metro/index.js` `OPTIONAL_MODULES` and `tsup.config.ts` now treat `expo-blur` as a guarded
|
|
99
|
+
optional peer (moved off the static-peer list), so the built `dist/` collects it as optional and
|
|
100
|
+
the metro helper's empty-module safety net covers it for hosts that disable Metro's
|
|
101
|
+
`allowOptionalDependencies`.
|
|
102
|
+
|
|
6
103
|
## [0.14.1] - 2026-08-18
|
|
7
104
|
|
|
8
105
|
Two rulings from the 0.14.0 audit, applied. Both were reproduced there and left for a decision
|
package/README.md
CHANGED
|
@@ -801,8 +801,12 @@ consuming app, since the kit has no local TS install).
|
|
|
801
801
|
|
|
802
802
|
The kit ships a performance-first guided-tour engine on a **subpath** so the main
|
|
803
803
|
barrel stays dependency-free — import it from `@wireai/activation/coachmarks`. It
|
|
804
|
-
|
|
805
|
-
(UI-thread gesture + ring animation) and `expo-blur` (the frosted spotlight).
|
|
804
|
+
can USE two **optional** peers, and needs neither: `react-native-reanimated`
|
|
805
|
+
(UI-thread gesture + ring animation) and `expo-blur` (the frosted spotlight). Both
|
|
806
|
+
sit behind a guarded lazy require, so importing the subpath never pulls them into
|
|
807
|
+
your bundle. Without reanimated the tour renders statically (the ring, tooltip,
|
|
808
|
+
copy and gestures are unchanged — nothing animates); without expo-blur the frost
|
|
809
|
+
becomes a plain dimmed scrim.
|
|
806
810
|
Source consumers get it through the existing `@wireai/activation/*` tsconfig path
|
|
807
811
|
mapping; installed apps resolve the subpath export.
|
|
808
812
|
|
|
@@ -922,6 +926,12 @@ theme colors, and gates once through the same storage as the tours
|
|
|
922
926
|
(`wire_showcase_<id>_seen`; `isTestingCoachmark` bypasses it). If already seen it
|
|
923
927
|
renders nothing and calls `onDone` from an effect.
|
|
924
928
|
|
|
929
|
+
The pager is a guarded lazy require, so importing `@wireai/activation/showcase`
|
|
930
|
+
does not pull it into your bundle. If you never install it the showcase renders
|
|
931
|
+
nothing and calls `onDone` — your flow advances exactly as if the deck had been
|
|
932
|
+
seen — and the seen gate is NOT written, so installing the peer later still plays
|
|
933
|
+
the deck once.
|
|
934
|
+
|
|
925
935
|
**Recommended placement (v0.3.0+): AFTER `onComplete`, as a personalized value
|
|
926
936
|
bridge.** The user just told you what they want in `result.answers`, so the
|
|
927
937
|
terminal recap is the highest-value slot to show the 2-3 slides that answer it,
|
|
@@ -245,10 +245,18 @@ declare const selectTourSteps: <T extends {
|
|
|
245
245
|
}>(catalog: T[], selection?: string[]) => T[];
|
|
246
246
|
|
|
247
247
|
/**
|
|
248
|
-
* Abstract gesture pictogram shown beside a coachmark tooltip.
|
|
249
|
-
* every animation runs on the UI thread
|
|
250
|
-
* setState, so it costs nothing on the JS
|
|
251
|
-
* dropping the motion loop and rendering the
|
|
248
|
+
* Abstract gesture pictogram shown beside a coachmark tooltip. With the OPTIONAL
|
|
249
|
+
* `react-native-reanimated` peer installed every animation runs on the UI thread
|
|
250
|
+
* (shared values + worklets), never setState, so it costs nothing on the JS
|
|
251
|
+
* thread. Honors Reduce Motion by dropping the motion loop and rendering the
|
|
252
|
+
* glyph static.
|
|
253
|
+
*
|
|
254
|
+
* The peer is reached through the guarded lazy require in `reanimated.ts`, never
|
|
255
|
+
* a static import, so `@wireai/activation/coachmarks` (and `/showcase`, which
|
|
256
|
+
* reaches this component through FeatureShowcase) builds on a host that never
|
|
257
|
+
* installed it. Absent, the glyph renders STATICALLY at its resting frame — the
|
|
258
|
+
* same capsules, ring, dot and motion track, at full opacity and no travel, with
|
|
259
|
+
* one render instead of a UI-thread loop. The pictogram never disappears.
|
|
252
260
|
*
|
|
253
261
|
* NO drawn-hand anatomy. Swipes render the standard two-finger swipe glyph: two
|
|
254
262
|
* rounded vertical capsules (an abstract index + middle finger, slightly
|
|
@@ -245,10 +245,18 @@ declare const selectTourSteps: <T extends {
|
|
|
245
245
|
}>(catalog: T[], selection?: string[]) => T[];
|
|
246
246
|
|
|
247
247
|
/**
|
|
248
|
-
* Abstract gesture pictogram shown beside a coachmark tooltip.
|
|
249
|
-
* every animation runs on the UI thread
|
|
250
|
-
* setState, so it costs nothing on the JS
|
|
251
|
-
* dropping the motion loop and rendering the
|
|
248
|
+
* Abstract gesture pictogram shown beside a coachmark tooltip. With the OPTIONAL
|
|
249
|
+
* `react-native-reanimated` peer installed every animation runs on the UI thread
|
|
250
|
+
* (shared values + worklets), never setState, so it costs nothing on the JS
|
|
251
|
+
* thread. Honors Reduce Motion by dropping the motion loop and rendering the
|
|
252
|
+
* glyph static.
|
|
253
|
+
*
|
|
254
|
+
* The peer is reached through the guarded lazy require in `reanimated.ts`, never
|
|
255
|
+
* a static import, so `@wireai/activation/coachmarks` (and `/showcase`, which
|
|
256
|
+
* reaches this component through FeatureShowcase) builds on a host that never
|
|
257
|
+
* installed it. Absent, the glyph renders STATICALLY at its resting frame — the
|
|
258
|
+
* same capsules, ring, dot and motion track, at full opacity and no travel, with
|
|
259
|
+
* one render instead of a UI-thread loop. The pictogram never disappears.
|
|
252
260
|
*
|
|
253
261
|
* NO drawn-hand anatomy. Swipes render the standard two-finger swipe glyph: two
|
|
254
262
|
* rounded vertical capsules (an abstract index + middle finger, slightly
|
package/dist/coachmarks/index.js
CHANGED
|
@@ -2,16 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
var React3 = require('react');
|
|
4
4
|
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
-
var expoBlur = require('expo-blur');
|
|
6
5
|
var reactNative = require('react-native');
|
|
7
|
-
var Animated = require('react-native-reanimated');
|
|
8
6
|
|
|
9
7
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
8
|
|
|
11
9
|
var React3__default = /*#__PURE__*/_interopDefault(React3);
|
|
12
|
-
var Animated__default = /*#__PURE__*/_interopDefault(Animated);
|
|
13
10
|
|
|
14
|
-
|
|
11
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
12
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
13
|
+
}) : x)(function(x) {
|
|
14
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
15
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
16
|
+
});
|
|
15
17
|
|
|
16
18
|
// src/features/defaults.ts
|
|
17
19
|
var defaultWireFeatures = Object.freeze({
|
|
@@ -90,8 +92,8 @@ var DEFAULT_TIMEOUT_MS = 4e3;
|
|
|
90
92
|
var failOpen = async (storage, key) => {
|
|
91
93
|
var _a;
|
|
92
94
|
if (!storage) return defaultWireFeatures;
|
|
93
|
-
const
|
|
94
|
-
return (_a =
|
|
95
|
+
const cached3 = await readCachedFeatures(storage, key);
|
|
96
|
+
return (_a = cached3 == null ? void 0 : cached3.features) != null ? _a : defaultWireFeatures;
|
|
95
97
|
};
|
|
96
98
|
var fetchWithTimeout = async (url, apiKey, timeoutMs) => {
|
|
97
99
|
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
@@ -329,6 +331,198 @@ var useOnboardingTheme = () => {
|
|
|
329
331
|
const ctx = React3.useContext(OnboardingThemeContext);
|
|
330
332
|
return ctx != null ? ctx : mergeTheme();
|
|
331
333
|
};
|
|
334
|
+
|
|
335
|
+
// src/coachmarks/expoBlur.ts
|
|
336
|
+
var runtimeRequire = (moduleName) => {
|
|
337
|
+
if (moduleName !== "expo-blur") return void 0;
|
|
338
|
+
if (typeof __require !== "function") return void 0;
|
|
339
|
+
try {
|
|
340
|
+
return require("expo-blur");
|
|
341
|
+
} catch {
|
|
342
|
+
return void 0;
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
var interop = (mod) => {
|
|
346
|
+
if (!mod || typeof mod !== "object") return void 0;
|
|
347
|
+
const ns = mod;
|
|
348
|
+
if (ns.BlurView) return ns;
|
|
349
|
+
const def = mod.default;
|
|
350
|
+
if (def && typeof def === "object") return def;
|
|
351
|
+
return ns;
|
|
352
|
+
};
|
|
353
|
+
var asComponent = (value) => {
|
|
354
|
+
if (typeof value === "function") return value;
|
|
355
|
+
if (value && typeof value === "object" && "$$typeof" in value) {
|
|
356
|
+
return value;
|
|
357
|
+
}
|
|
358
|
+
return void 0;
|
|
359
|
+
};
|
|
360
|
+
var cached;
|
|
361
|
+
var resolveBlurView = (requireModule = runtimeRequire) => {
|
|
362
|
+
try {
|
|
363
|
+
if (cached === void 0 || requireModule !== runtimeRequire) {
|
|
364
|
+
const resolved = interop(requireModule("expo-blur"));
|
|
365
|
+
const component = resolved ? asComponent(resolved.BlurView) : void 0;
|
|
366
|
+
if (requireModule === runtimeRequire) cached = component != null ? component : null;
|
|
367
|
+
return component;
|
|
368
|
+
}
|
|
369
|
+
if (cached === null) return void 0;
|
|
370
|
+
return cached;
|
|
371
|
+
} catch {
|
|
372
|
+
return void 0;
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
var lastKnown = false;
|
|
376
|
+
var useReducedMotion = () => {
|
|
377
|
+
const [reduced, setReduced] = React3.useState(lastKnown);
|
|
378
|
+
React3.useEffect(() => {
|
|
379
|
+
let mounted = true;
|
|
380
|
+
reactNative.AccessibilityInfo.isReduceMotionEnabled().then((value) => {
|
|
381
|
+
lastKnown = value;
|
|
382
|
+
if (mounted) setReduced(value);
|
|
383
|
+
}).catch(() => {
|
|
384
|
+
});
|
|
385
|
+
const sub = reactNative.AccessibilityInfo.addEventListener("reduceMotionChanged", (value) => {
|
|
386
|
+
lastKnown = value;
|
|
387
|
+
setReduced(value);
|
|
388
|
+
});
|
|
389
|
+
return () => {
|
|
390
|
+
mounted = false;
|
|
391
|
+
sub == null ? void 0 : sub.remove();
|
|
392
|
+
};
|
|
393
|
+
}, []);
|
|
394
|
+
return reduced;
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
// src/coachmarks/reanimated.ts
|
|
398
|
+
var runtimeRequire2 = (moduleName) => {
|
|
399
|
+
if (moduleName !== "react-native-reanimated") return void 0;
|
|
400
|
+
if (typeof __require !== "function") return void 0;
|
|
401
|
+
try {
|
|
402
|
+
return require("react-native-reanimated");
|
|
403
|
+
} catch {
|
|
404
|
+
return void 0;
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
var useStaticSharedValue = (initial) => {
|
|
408
|
+
const [, bump] = React3.useReducer((n) => n + 1, 0);
|
|
409
|
+
const box = React3.useRef(null);
|
|
410
|
+
if (box.current === null) {
|
|
411
|
+
let current2 = initial;
|
|
412
|
+
box.current = {
|
|
413
|
+
get value() {
|
|
414
|
+
return current2;
|
|
415
|
+
},
|
|
416
|
+
set value(next) {
|
|
417
|
+
if (Object.is(next, current2)) return;
|
|
418
|
+
current2 = next;
|
|
419
|
+
bump();
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
return box.current;
|
|
424
|
+
};
|
|
425
|
+
var runWorklet = (factory, fallback) => {
|
|
426
|
+
var _a;
|
|
427
|
+
try {
|
|
428
|
+
return (_a = factory()) != null ? _a : fallback;
|
|
429
|
+
} catch {
|
|
430
|
+
return fallback;
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
var StaticAnimatedView = React3.forwardRef(function StaticAnimatedView2({ entering: _entering, exiting: _exiting, children, ...rest }, ref) {
|
|
434
|
+
return React3.createElement(reactNative.View, { ...rest, ref }, children);
|
|
435
|
+
});
|
|
436
|
+
var staticCreateAnimatedComponent = (component) => function StaticAnimatedComponent({ animatedProps, ...rest }) {
|
|
437
|
+
return React3.createElement(component, { ...rest, ...animatedProps });
|
|
438
|
+
};
|
|
439
|
+
var staticApi = {
|
|
440
|
+
present: false,
|
|
441
|
+
View: StaticAnimatedView,
|
|
442
|
+
createAnimatedComponent: staticCreateAnimatedComponent,
|
|
443
|
+
useSharedValue: useStaticSharedValue,
|
|
444
|
+
useAnimatedStyle: (factory) => runWorklet(factory, {}),
|
|
445
|
+
useAnimatedProps: (factory) => runWorklet(factory, {}),
|
|
446
|
+
useReducedMotion,
|
|
447
|
+
withTiming: (toValue) => toValue,
|
|
448
|
+
withRepeat: (animation) => animation,
|
|
449
|
+
withSequence: (...animations) => {
|
|
450
|
+
var _a;
|
|
451
|
+
return (_a = animations[animations.length - 1]) != null ? _a : 0;
|
|
452
|
+
},
|
|
453
|
+
withDelay: (_ms, animation) => animation,
|
|
454
|
+
fadeIn: () => void 0,
|
|
455
|
+
fadeOut: () => void 0
|
|
456
|
+
};
|
|
457
|
+
var isFunction = (value) => typeof value === "function";
|
|
458
|
+
var isComponent = (value) => typeof value === "function" || !!value && typeof value === "object" && "$$typeof" in value;
|
|
459
|
+
var fromModule = (mod) => {
|
|
460
|
+
var _a, _b;
|
|
461
|
+
if (!mod || typeof mod !== "object") return void 0;
|
|
462
|
+
const ns = mod;
|
|
463
|
+
const def = ns.default && typeof ns.default === "object" ? ns.default : void 0;
|
|
464
|
+
const pick = (name) => {
|
|
465
|
+
var _a2;
|
|
466
|
+
return (_a2 = ns[name]) != null ? _a2 : def == null ? void 0 : def[name];
|
|
467
|
+
};
|
|
468
|
+
const view = (_a = def == null ? void 0 : def.View) != null ? _a : ns.View;
|
|
469
|
+
const createAnimated = (_b = def == null ? void 0 : def.createAnimatedComponent) != null ? _b : ns.createAnimatedComponent;
|
|
470
|
+
const hooks = {
|
|
471
|
+
useSharedValue: pick("useSharedValue"),
|
|
472
|
+
useAnimatedStyle: pick("useAnimatedStyle"),
|
|
473
|
+
useAnimatedProps: pick("useAnimatedProps"),
|
|
474
|
+
useReducedMotion: pick("useReducedMotion"),
|
|
475
|
+
withTiming: pick("withTiming"),
|
|
476
|
+
withRepeat: pick("withRepeat"),
|
|
477
|
+
withSequence: pick("withSequence"),
|
|
478
|
+
withDelay: pick("withDelay")
|
|
479
|
+
};
|
|
480
|
+
const fadeIn = pick("FadeIn");
|
|
481
|
+
const fadeOut = pick("FadeOut");
|
|
482
|
+
if (!isComponent(view) || !isFunction(createAnimated)) return void 0;
|
|
483
|
+
if (Object.values(hooks).some((member) => !isFunction(member))) return void 0;
|
|
484
|
+
if (!fadeIn || !fadeOut) return void 0;
|
|
485
|
+
const api2 = {
|
|
486
|
+
present: true,
|
|
487
|
+
View: view,
|
|
488
|
+
createAnimatedComponent: createAnimated,
|
|
489
|
+
...hooks,
|
|
490
|
+
fadeIn: (durationMs) => fadeIn.duration(durationMs),
|
|
491
|
+
fadeOut: (durationMs) => fadeOut.duration(durationMs)
|
|
492
|
+
};
|
|
493
|
+
return api2;
|
|
494
|
+
};
|
|
495
|
+
var cached2;
|
|
496
|
+
var resolveReanimated = (requireModule = runtimeRequire2) => {
|
|
497
|
+
var _a;
|
|
498
|
+
try {
|
|
499
|
+
if (cached2 === void 0 || requireModule !== runtimeRequire2) {
|
|
500
|
+
const resolved = (_a = fromModule(requireModule("react-native-reanimated"))) != null ? _a : staticApi;
|
|
501
|
+
if (requireModule === runtimeRequire2) cached2 = resolved;
|
|
502
|
+
return resolved;
|
|
503
|
+
}
|
|
504
|
+
return cached2;
|
|
505
|
+
} catch {
|
|
506
|
+
return staticApi;
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
var api = () => resolveReanimated();
|
|
510
|
+
var Animated = {
|
|
511
|
+
get View() {
|
|
512
|
+
return api().View;
|
|
513
|
+
},
|
|
514
|
+
createAnimatedComponent: (component) => api().createAnimatedComponent(component)
|
|
515
|
+
};
|
|
516
|
+
var FadeIn = { duration: (durationMs) => api().fadeIn(durationMs) };
|
|
517
|
+
var FadeOut = { duration: (durationMs) => api().fadeOut(durationMs) };
|
|
518
|
+
var useSharedValue = (initial) => api().useSharedValue(initial);
|
|
519
|
+
var useAnimatedStyle = (factory) => api().useAnimatedStyle(factory);
|
|
520
|
+
var useAnimatedProps = (factory) => api().useAnimatedProps(factory);
|
|
521
|
+
var useReducedMotion2 = () => api().useReducedMotion();
|
|
522
|
+
var withTiming = (toValue, config) => api().withTiming(toValue, config);
|
|
523
|
+
var withRepeat = (animation, count, reverse) => api().withRepeat(animation, count, reverse);
|
|
524
|
+
var withSequence = (...animations) => api().withSequence(...animations);
|
|
525
|
+
var withDelay = (ms, animation) => api().withDelay(ms, animation);
|
|
332
526
|
var SWIPE_MS = 700;
|
|
333
527
|
var TAP_MS = 320;
|
|
334
528
|
var SPREAD_MS = 620;
|
|
@@ -386,29 +580,29 @@ var GestureHintComponent = ({
|
|
|
386
580
|
renderHand
|
|
387
581
|
}) => {
|
|
388
582
|
var _a;
|
|
389
|
-
const translate =
|
|
390
|
-
const opacity =
|
|
391
|
-
const pulse =
|
|
392
|
-
const spread =
|
|
393
|
-
const reduced =
|
|
583
|
+
const translate = useSharedValue(0);
|
|
584
|
+
const opacity = useSharedValue(1);
|
|
585
|
+
const pulse = useSharedValue(1);
|
|
586
|
+
const spread = useSharedValue(0);
|
|
587
|
+
const reduced = useReducedMotion2();
|
|
394
588
|
const isSwipe = type === "swipe_up" || type === "swipe_down" || type === "swipe_left" || type === "swipe_right";
|
|
395
589
|
const isSpread = type === "pinch" || type === "zoom";
|
|
396
590
|
const isTap = type === "tap" || type === "double_tap";
|
|
397
591
|
React3.useEffect(() => {
|
|
398
592
|
if (type === "none" || reduced) return void 0;
|
|
399
593
|
if (isTap) {
|
|
400
|
-
pulse.value =
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
594
|
+
pulse.value = withRepeat(
|
|
595
|
+
withSequence(
|
|
596
|
+
withTiming(0.82, { duration: TAP_MS }),
|
|
597
|
+
withTiming(1, { duration: TAP_MS })
|
|
404
598
|
),
|
|
405
599
|
-1,
|
|
406
600
|
false
|
|
407
601
|
);
|
|
408
|
-
opacity.value =
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
602
|
+
opacity.value = withRepeat(
|
|
603
|
+
withSequence(
|
|
604
|
+
withTiming(0.55, { duration: TAP_MS }),
|
|
605
|
+
withTiming(1, { duration: TAP_MS })
|
|
412
606
|
),
|
|
413
607
|
-1,
|
|
414
608
|
false
|
|
@@ -416,48 +610,48 @@ var GestureHintComponent = ({
|
|
|
416
610
|
return void 0;
|
|
417
611
|
}
|
|
418
612
|
if (isSpread) {
|
|
419
|
-
spread.value =
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
613
|
+
spread.value = withRepeat(
|
|
614
|
+
withSequence(
|
|
615
|
+
withTiming(size * 0.26, { duration: SPREAD_MS }),
|
|
616
|
+
withTiming(0, { duration: SPREAD_MS })
|
|
423
617
|
),
|
|
424
618
|
-1,
|
|
425
619
|
false
|
|
426
620
|
);
|
|
427
621
|
return void 0;
|
|
428
622
|
}
|
|
429
|
-
translate.value =
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
623
|
+
translate.value = withRepeat(
|
|
624
|
+
withSequence(
|
|
625
|
+
withTiming(0, { duration: 0 }),
|
|
626
|
+
withTiming(-26, { duration: SWIPE_MS }),
|
|
627
|
+
withDelay(140, withTiming(0, { duration: 0 }))
|
|
434
628
|
),
|
|
435
629
|
-1,
|
|
436
630
|
false
|
|
437
631
|
);
|
|
438
|
-
opacity.value =
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
632
|
+
opacity.value = withRepeat(
|
|
633
|
+
withSequence(
|
|
634
|
+
withTiming(1, { duration: SWIPE_MS / 2 }),
|
|
635
|
+
withTiming(0, { duration: SWIPE_MS / 2 }),
|
|
636
|
+
withDelay(140, withTiming(1, { duration: 0 }))
|
|
443
637
|
),
|
|
444
638
|
-1,
|
|
445
639
|
false
|
|
446
640
|
);
|
|
447
641
|
return void 0;
|
|
448
642
|
}, [type, reduced, isTap, isSpread, translate, opacity, pulse, spread, size]);
|
|
449
|
-
const swipeAnimatedStyle =
|
|
643
|
+
const swipeAnimatedStyle = useAnimatedStyle(() => ({
|
|
450
644
|
opacity: opacity.value,
|
|
451
645
|
transform: [{ translateY: translate.value }]
|
|
452
646
|
}));
|
|
453
|
-
const tapAnimatedStyle =
|
|
647
|
+
const tapAnimatedStyle = useAnimatedStyle(() => ({
|
|
454
648
|
opacity: opacity.value,
|
|
455
649
|
transform: [{ scale: pulse.value }]
|
|
456
650
|
}));
|
|
457
|
-
const spreadLeftStyle =
|
|
651
|
+
const spreadLeftStyle = useAnimatedStyle(() => ({
|
|
458
652
|
transform: [{ translateX: -spread.value }]
|
|
459
653
|
}));
|
|
460
|
-
const spreadRightStyle =
|
|
654
|
+
const spreadRightStyle = useAnimatedStyle(() => ({
|
|
461
655
|
transform: [{ translateX: spread.value }]
|
|
462
656
|
}));
|
|
463
657
|
if (type === "none") return null;
|
|
@@ -469,7 +663,7 @@ var GestureHintComponent = ({
|
|
|
469
663
|
style: { transform: [{ rotate: (_a = GLYPH_ROTATION[type]) != null ? _a : "0deg" }] },
|
|
470
664
|
pointerEvents: "none",
|
|
471
665
|
children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles.swipeStack, children: [
|
|
472
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
666
|
+
/* @__PURE__ */ jsxRuntime.jsx(Animated.View, { style: swipeAnimatedStyle, pointerEvents: "none", children: customGlyph != null ? customGlyph : /* @__PURE__ */ jsxRuntime.jsx(FingerPair, { size, color }) }),
|
|
473
667
|
/* @__PURE__ */ jsxRuntime.jsx(MotionTrack, { size, color })
|
|
474
668
|
] })
|
|
475
669
|
}
|
|
@@ -484,16 +678,16 @@ var GestureHintComponent = ({
|
|
|
484
678
|
backgroundColor: color
|
|
485
679
|
};
|
|
486
680
|
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles.spreadRow, { gap: size * 0.12 }], pointerEvents: "none", children: [
|
|
487
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
488
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
681
|
+
/* @__PURE__ */ jsxRuntime.jsx(Animated.View, { style: spreadLeftStyle, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: capsule }) }),
|
|
682
|
+
/* @__PURE__ */ jsxRuntime.jsx(Animated.View, { style: spreadRightStyle, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: capsule }) })
|
|
489
683
|
] });
|
|
490
684
|
}
|
|
491
685
|
const ringSize = size * 0.86;
|
|
492
686
|
const ringBorder = Math.max(2, size * 0.08);
|
|
493
687
|
const dotSize = size * 0.32;
|
|
494
|
-
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles.tapWrap, { width: size, height: size }], pointerEvents: "none", children: customGlyph ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
688
|
+
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles.tapWrap, { width: size, height: size }], pointerEvents: "none", children: customGlyph ? /* @__PURE__ */ jsxRuntime.jsx(Animated.View, { style: tapAnimatedStyle, pointerEvents: "none", children: customGlyph }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
495
689
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
496
|
-
|
|
690
|
+
Animated.View,
|
|
497
691
|
{
|
|
498
692
|
style: [
|
|
499
693
|
styles.tapRing,
|
|
@@ -543,7 +737,6 @@ var styles = reactNative.StyleSheet.create({
|
|
|
543
737
|
});
|
|
544
738
|
var GestureHint = React3__default.default.memo(GestureHintComponent);
|
|
545
739
|
GestureHint.displayName = "GestureHint";
|
|
546
|
-
var AnimatedBlurView = Animated__default.default.createAnimatedComponent(expoBlur.BlurView);
|
|
547
740
|
var BLUR_INTENSITY = 26;
|
|
548
741
|
var FADE_MS = 280;
|
|
549
742
|
var RING_PADDING = 10;
|
|
@@ -561,31 +754,35 @@ var SpotlightOverlayComponent = ({
|
|
|
561
754
|
accentTextColor
|
|
562
755
|
}) => {
|
|
563
756
|
const { height: screenH } = reactNative.useWindowDimensions();
|
|
564
|
-
const reduced =
|
|
757
|
+
const reduced = useReducedMotion2();
|
|
565
758
|
const theme = useOnboardingTheme();
|
|
566
759
|
const accent = accentColor != null ? accentColor : theme.colors.primary;
|
|
567
760
|
const accentText = accentTextColor != null ? accentTextColor : theme.colors.onPrimary;
|
|
761
|
+
const AnimatedBlurView = React3.useMemo(() => {
|
|
762
|
+
const BlurView = resolveBlurView();
|
|
763
|
+
return BlurView ? Animated.createAnimatedComponent(BlurView) : null;
|
|
764
|
+
}, []);
|
|
568
765
|
const showGesture = gesture !== "none" && gesture !== "tap" && gesture !== "double_tap";
|
|
569
|
-
const intensity =
|
|
570
|
-
const ringScale =
|
|
766
|
+
const intensity = useSharedValue(0);
|
|
767
|
+
const ringScale = useSharedValue(1);
|
|
571
768
|
React3.useEffect(() => {
|
|
572
|
-
intensity.value =
|
|
769
|
+
intensity.value = withTiming(BLUR_INTENSITY, { duration: FADE_MS });
|
|
573
770
|
}, [intensity]);
|
|
574
771
|
React3.useEffect(() => {
|
|
575
772
|
if (reduced) return;
|
|
576
|
-
ringScale.value =
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
773
|
+
ringScale.value = withRepeat(
|
|
774
|
+
withSequence(
|
|
775
|
+
withTiming(1.06, { duration: 700 }),
|
|
776
|
+
withTiming(1, { duration: 700 })
|
|
580
777
|
),
|
|
581
778
|
-1,
|
|
582
779
|
true
|
|
583
780
|
);
|
|
584
781
|
}, [reduced, ringScale]);
|
|
585
|
-
const blurAnimatedProps =
|
|
782
|
+
const blurAnimatedProps = useAnimatedProps(() => ({
|
|
586
783
|
intensity: intensity.value
|
|
587
784
|
}));
|
|
588
|
-
const ringAnimatedStyle =
|
|
785
|
+
const ringAnimatedStyle = useAnimatedStyle(() => ({
|
|
589
786
|
transform: [{ scale: ringScale.value }]
|
|
590
787
|
}));
|
|
591
788
|
const geometry = React3.useMemo(() => {
|
|
@@ -607,19 +804,29 @@ var SpotlightOverlayComponent = ({
|
|
|
607
804
|
};
|
|
608
805
|
}, [targetRect, placement, screenH]);
|
|
609
806
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
610
|
-
|
|
807
|
+
Animated.View,
|
|
611
808
|
{
|
|
612
809
|
style: [reactNative.StyleSheet.absoluteFill, styles2.root],
|
|
613
|
-
entering:
|
|
614
|
-
exiting:
|
|
810
|
+
entering: FadeIn.duration(FADE_MS),
|
|
811
|
+
exiting: FadeOut.duration(FADE_MS),
|
|
615
812
|
children: [
|
|
616
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
813
|
+
AnimatedBlurView ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
617
814
|
AnimatedBlurView,
|
|
618
815
|
{
|
|
619
816
|
tint: "dark",
|
|
620
817
|
animatedProps: blurAnimatedProps,
|
|
621
818
|
style: reactNative.StyleSheet.absoluteFill
|
|
622
819
|
}
|
|
820
|
+
) : (
|
|
821
|
+
// `expo-blur` absent → a plain dimmed scrim stands in for the frost. Same dark backdrop
|
|
822
|
+
// the tour reads against, so the ring, tooltip and gestures behave identically.
|
|
823
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
824
|
+
Animated.View,
|
|
825
|
+
{
|
|
826
|
+
style: [reactNative.StyleSheet.absoluteFill, styles2.scrim],
|
|
827
|
+
pointerEvents: "none"
|
|
828
|
+
}
|
|
829
|
+
)
|
|
623
830
|
),
|
|
624
831
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
625
832
|
reactNative.Pressable,
|
|
@@ -631,7 +838,7 @@ var SpotlightOverlayComponent = ({
|
|
|
631
838
|
}
|
|
632
839
|
),
|
|
633
840
|
geometry && /* @__PURE__ */ jsxRuntime.jsx(
|
|
634
|
-
|
|
841
|
+
Animated.View,
|
|
635
842
|
{
|
|
636
843
|
style: [
|
|
637
844
|
styles2.ring,
|
|
@@ -686,6 +893,11 @@ var styles2 = reactNative.StyleSheet.create({
|
|
|
686
893
|
zIndex: 9999,
|
|
687
894
|
elevation: 9999
|
|
688
895
|
},
|
|
896
|
+
// Fallback backdrop when `expo-blur` is absent. A structural dim (not a brand token), tuned to
|
|
897
|
+
// read like the dark blur at BLUR_INTENSITY so the ring + tooltip keep the same contrast.
|
|
898
|
+
scrim: {
|
|
899
|
+
backgroundColor: "rgba(0, 0, 0, 0.55)"
|
|
900
|
+
},
|
|
689
901
|
ring: {
|
|
690
902
|
position: "absolute",
|
|
691
903
|
borderWidth: 2.5,
|