@wireai/activation 0.14.2 → 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 +71 -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 +205 -56
- package/dist/coachmarks/index.js.map +1 -1
- package/dist/coachmarks/index.mjs +157 -7
- 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 +16 -5
- package/package.json +1 -1
- package/src/coachmarks/GestureHint.tsx +16 -7
- package/src/coachmarks/SpotlightOverlay.tsx +19 -13
- 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,77 @@
|
|
|
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
|
+
|
|
6
77
|
## [0.14.2] - 2026-08-18
|
|
7
78
|
|
|
8
79
|
**PATCH: no public surface gained, lost or renamed a member.** `SpotlightOverlay` keeps its props
|
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
|
@@ -3,12 +3,10 @@
|
|
|
3
3
|
var React3 = require('react');
|
|
4
4
|
var jsxRuntime = require('react/jsx-runtime');
|
|
5
5
|
var reactNative = require('react-native');
|
|
6
|
-
var Animated = require('react-native-reanimated');
|
|
7
6
|
|
|
8
7
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
8
|
|
|
10
9
|
var React3__default = /*#__PURE__*/_interopDefault(React3);
|
|
11
|
-
var Animated__default = /*#__PURE__*/_interopDefault(Animated);
|
|
12
10
|
|
|
13
11
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
14
12
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
@@ -94,8 +92,8 @@ var DEFAULT_TIMEOUT_MS = 4e3;
|
|
|
94
92
|
var failOpen = async (storage, key) => {
|
|
95
93
|
var _a;
|
|
96
94
|
if (!storage) return defaultWireFeatures;
|
|
97
|
-
const
|
|
98
|
-
return (_a =
|
|
95
|
+
const cached3 = await readCachedFeatures(storage, key);
|
|
96
|
+
return (_a = cached3 == null ? void 0 : cached3.features) != null ? _a : defaultWireFeatures;
|
|
99
97
|
};
|
|
100
98
|
var fetchWithTimeout = async (url, apiKey, timeoutMs) => {
|
|
101
99
|
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
@@ -374,6 +372,157 @@ var resolveBlurView = (requireModule = runtimeRequire) => {
|
|
|
374
372
|
return void 0;
|
|
375
373
|
}
|
|
376
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);
|
|
377
526
|
var SWIPE_MS = 700;
|
|
378
527
|
var TAP_MS = 320;
|
|
379
528
|
var SPREAD_MS = 620;
|
|
@@ -431,29 +580,29 @@ var GestureHintComponent = ({
|
|
|
431
580
|
renderHand
|
|
432
581
|
}) => {
|
|
433
582
|
var _a;
|
|
434
|
-
const translate =
|
|
435
|
-
const opacity =
|
|
436
|
-
const pulse =
|
|
437
|
-
const spread =
|
|
438
|
-
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();
|
|
439
588
|
const isSwipe = type === "swipe_up" || type === "swipe_down" || type === "swipe_left" || type === "swipe_right";
|
|
440
589
|
const isSpread = type === "pinch" || type === "zoom";
|
|
441
590
|
const isTap = type === "tap" || type === "double_tap";
|
|
442
591
|
React3.useEffect(() => {
|
|
443
592
|
if (type === "none" || reduced) return void 0;
|
|
444
593
|
if (isTap) {
|
|
445
|
-
pulse.value =
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
594
|
+
pulse.value = withRepeat(
|
|
595
|
+
withSequence(
|
|
596
|
+
withTiming(0.82, { duration: TAP_MS }),
|
|
597
|
+
withTiming(1, { duration: TAP_MS })
|
|
449
598
|
),
|
|
450
599
|
-1,
|
|
451
600
|
false
|
|
452
601
|
);
|
|
453
|
-
opacity.value =
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
602
|
+
opacity.value = withRepeat(
|
|
603
|
+
withSequence(
|
|
604
|
+
withTiming(0.55, { duration: TAP_MS }),
|
|
605
|
+
withTiming(1, { duration: TAP_MS })
|
|
457
606
|
),
|
|
458
607
|
-1,
|
|
459
608
|
false
|
|
@@ -461,48 +610,48 @@ var GestureHintComponent = ({
|
|
|
461
610
|
return void 0;
|
|
462
611
|
}
|
|
463
612
|
if (isSpread) {
|
|
464
|
-
spread.value =
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
613
|
+
spread.value = withRepeat(
|
|
614
|
+
withSequence(
|
|
615
|
+
withTiming(size * 0.26, { duration: SPREAD_MS }),
|
|
616
|
+
withTiming(0, { duration: SPREAD_MS })
|
|
468
617
|
),
|
|
469
618
|
-1,
|
|
470
619
|
false
|
|
471
620
|
);
|
|
472
621
|
return void 0;
|
|
473
622
|
}
|
|
474
|
-
translate.value =
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
623
|
+
translate.value = withRepeat(
|
|
624
|
+
withSequence(
|
|
625
|
+
withTiming(0, { duration: 0 }),
|
|
626
|
+
withTiming(-26, { duration: SWIPE_MS }),
|
|
627
|
+
withDelay(140, withTiming(0, { duration: 0 }))
|
|
479
628
|
),
|
|
480
629
|
-1,
|
|
481
630
|
false
|
|
482
631
|
);
|
|
483
|
-
opacity.value =
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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 }))
|
|
488
637
|
),
|
|
489
638
|
-1,
|
|
490
639
|
false
|
|
491
640
|
);
|
|
492
641
|
return void 0;
|
|
493
642
|
}, [type, reduced, isTap, isSpread, translate, opacity, pulse, spread, size]);
|
|
494
|
-
const swipeAnimatedStyle =
|
|
643
|
+
const swipeAnimatedStyle = useAnimatedStyle(() => ({
|
|
495
644
|
opacity: opacity.value,
|
|
496
645
|
transform: [{ translateY: translate.value }]
|
|
497
646
|
}));
|
|
498
|
-
const tapAnimatedStyle =
|
|
647
|
+
const tapAnimatedStyle = useAnimatedStyle(() => ({
|
|
499
648
|
opacity: opacity.value,
|
|
500
649
|
transform: [{ scale: pulse.value }]
|
|
501
650
|
}));
|
|
502
|
-
const spreadLeftStyle =
|
|
651
|
+
const spreadLeftStyle = useAnimatedStyle(() => ({
|
|
503
652
|
transform: [{ translateX: -spread.value }]
|
|
504
653
|
}));
|
|
505
|
-
const spreadRightStyle =
|
|
654
|
+
const spreadRightStyle = useAnimatedStyle(() => ({
|
|
506
655
|
transform: [{ translateX: spread.value }]
|
|
507
656
|
}));
|
|
508
657
|
if (type === "none") return null;
|
|
@@ -514,7 +663,7 @@ var GestureHintComponent = ({
|
|
|
514
663
|
style: { transform: [{ rotate: (_a = GLYPH_ROTATION[type]) != null ? _a : "0deg" }] },
|
|
515
664
|
pointerEvents: "none",
|
|
516
665
|
children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles.swipeStack, children: [
|
|
517
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
666
|
+
/* @__PURE__ */ jsxRuntime.jsx(Animated.View, { style: swipeAnimatedStyle, pointerEvents: "none", children: customGlyph != null ? customGlyph : /* @__PURE__ */ jsxRuntime.jsx(FingerPair, { size, color }) }),
|
|
518
667
|
/* @__PURE__ */ jsxRuntime.jsx(MotionTrack, { size, color })
|
|
519
668
|
] })
|
|
520
669
|
}
|
|
@@ -529,16 +678,16 @@ var GestureHintComponent = ({
|
|
|
529
678
|
backgroundColor: color
|
|
530
679
|
};
|
|
531
680
|
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles.spreadRow, { gap: size * 0.12 }], pointerEvents: "none", children: [
|
|
532
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
533
|
-
/* @__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 }) })
|
|
534
683
|
] });
|
|
535
684
|
}
|
|
536
685
|
const ringSize = size * 0.86;
|
|
537
686
|
const ringBorder = Math.max(2, size * 0.08);
|
|
538
687
|
const dotSize = size * 0.32;
|
|
539
|
-
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: [
|
|
540
689
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
541
|
-
|
|
690
|
+
Animated.View,
|
|
542
691
|
{
|
|
543
692
|
style: [
|
|
544
693
|
styles.tapRing,
|
|
@@ -605,35 +754,35 @@ var SpotlightOverlayComponent = ({
|
|
|
605
754
|
accentTextColor
|
|
606
755
|
}) => {
|
|
607
756
|
const { height: screenH } = reactNative.useWindowDimensions();
|
|
608
|
-
const reduced =
|
|
757
|
+
const reduced = useReducedMotion2();
|
|
609
758
|
const theme = useOnboardingTheme();
|
|
610
759
|
const accent = accentColor != null ? accentColor : theme.colors.primary;
|
|
611
760
|
const accentText = accentTextColor != null ? accentTextColor : theme.colors.onPrimary;
|
|
612
761
|
const AnimatedBlurView = React3.useMemo(() => {
|
|
613
762
|
const BlurView = resolveBlurView();
|
|
614
|
-
return BlurView ?
|
|
763
|
+
return BlurView ? Animated.createAnimatedComponent(BlurView) : null;
|
|
615
764
|
}, []);
|
|
616
765
|
const showGesture = gesture !== "none" && gesture !== "tap" && gesture !== "double_tap";
|
|
617
|
-
const intensity =
|
|
618
|
-
const ringScale =
|
|
766
|
+
const intensity = useSharedValue(0);
|
|
767
|
+
const ringScale = useSharedValue(1);
|
|
619
768
|
React3.useEffect(() => {
|
|
620
|
-
intensity.value =
|
|
769
|
+
intensity.value = withTiming(BLUR_INTENSITY, { duration: FADE_MS });
|
|
621
770
|
}, [intensity]);
|
|
622
771
|
React3.useEffect(() => {
|
|
623
772
|
if (reduced) return;
|
|
624
|
-
ringScale.value =
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
773
|
+
ringScale.value = withRepeat(
|
|
774
|
+
withSequence(
|
|
775
|
+
withTiming(1.06, { duration: 700 }),
|
|
776
|
+
withTiming(1, { duration: 700 })
|
|
628
777
|
),
|
|
629
778
|
-1,
|
|
630
779
|
true
|
|
631
780
|
);
|
|
632
781
|
}, [reduced, ringScale]);
|
|
633
|
-
const blurAnimatedProps =
|
|
782
|
+
const blurAnimatedProps = useAnimatedProps(() => ({
|
|
634
783
|
intensity: intensity.value
|
|
635
784
|
}));
|
|
636
|
-
const ringAnimatedStyle =
|
|
785
|
+
const ringAnimatedStyle = useAnimatedStyle(() => ({
|
|
637
786
|
transform: [{ scale: ringScale.value }]
|
|
638
787
|
}));
|
|
639
788
|
const geometry = React3.useMemo(() => {
|
|
@@ -655,11 +804,11 @@ var SpotlightOverlayComponent = ({
|
|
|
655
804
|
};
|
|
656
805
|
}, [targetRect, placement, screenH]);
|
|
657
806
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
658
|
-
|
|
807
|
+
Animated.View,
|
|
659
808
|
{
|
|
660
809
|
style: [reactNative.StyleSheet.absoluteFill, styles2.root],
|
|
661
|
-
entering:
|
|
662
|
-
exiting:
|
|
810
|
+
entering: FadeIn.duration(FADE_MS),
|
|
811
|
+
exiting: FadeOut.duration(FADE_MS),
|
|
663
812
|
children: [
|
|
664
813
|
AnimatedBlurView ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
665
814
|
AnimatedBlurView,
|
|
@@ -672,7 +821,7 @@ var SpotlightOverlayComponent = ({
|
|
|
672
821
|
// `expo-blur` absent → a plain dimmed scrim stands in for the frost. Same dark backdrop
|
|
673
822
|
// the tour reads against, so the ring, tooltip and gestures behave identically.
|
|
674
823
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
675
|
-
|
|
824
|
+
Animated.View,
|
|
676
825
|
{
|
|
677
826
|
style: [reactNative.StyleSheet.absoluteFill, styles2.scrim],
|
|
678
827
|
pointerEvents: "none"
|
|
@@ -689,7 +838,7 @@ var SpotlightOverlayComponent = ({
|
|
|
689
838
|
}
|
|
690
839
|
),
|
|
691
840
|
geometry && /* @__PURE__ */ jsxRuntime.jsx(
|
|
692
|
-
|
|
841
|
+
Animated.View,
|
|
693
842
|
{
|
|
694
843
|
style: [
|
|
695
844
|
styles2.ring,
|