@rootnative/inertia 0.0.9 → 0.0.11
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 +95 -1
- package/README.md +8 -8
- package/dist/{chunk-MFAFB4K7.mjs → chunk-2Q6JMDYZ.mjs} +8 -10
- package/dist/chunk-2UREDR4Z.js +8 -0
- package/dist/{chunk-ZBHQPVWE.js → chunk-3PB27UIE.js} +2 -2
- package/dist/{chunk-IX6SEOSK.mjs → chunk-5EYIZG72.mjs} +60 -45
- package/dist/{chunk-EN4PN7X3.mjs → chunk-7RMSLKQQ.mjs} +1 -1
- package/dist/{chunk-6YIUETBF.mjs → chunk-DT2KVAAO.mjs} +1 -1
- package/dist/{chunk-L4JFECXU.mjs → chunk-FKJLPYGY.mjs} +6 -3
- package/dist/{chunk-E3ALFSH2.js → chunk-HBURWGCZ.js} +86 -71
- package/dist/chunk-JAJHKJC5.js +8 -0
- package/dist/chunk-JBMTTICC.js +8 -0
- package/dist/{chunk-GSUC4HWP.mjs → chunk-JGQ6YRDN.mjs} +1 -1
- package/dist/chunk-KFHZMFAX.js +23 -0
- package/dist/{chunk-M4766VUV.mjs → chunk-LHW4VNWD.mjs} +1 -1
- package/dist/{chunk-MUT6BTZS.js → chunk-NTG6PSGZ.js} +10 -7
- package/dist/chunk-OH46TGEA.mjs +20 -0
- package/dist/{chunk-6AN5X3YE.js → chunk-QDMSFRQR.js} +10 -11
- package/dist/{chunk-W7NTRSPD.mjs → chunk-QIRKPDXA.mjs} +118 -68
- package/dist/{chunk-WNVHPMBI.js → chunk-QX5JHRZ2.js} +118 -66
- package/dist/{chunk-6NOCY764.js → chunk-R7YD6L3D.js} +2 -2
- package/dist/{chunk-JBK5ZLMH.mjs → chunk-RO4XFTQ6.mjs} +1 -1
- package/dist/{chunk-QNLSCOR5.mjs → chunk-UGYBFAVY.mjs} +1 -1
- package/dist/{chunk-IHVMNOS4.js → chunk-XLKI2MSD.js} +2 -2
- package/dist/gestureLayer/index.d.mts +3 -3
- package/dist/gestureLayer/index.d.ts +3 -3
- package/dist/gestureLayer/index.js +12 -18
- package/dist/gestureLayer/index.mjs +5 -11
- package/dist/index.d.mts +108 -49
- package/dist/index.d.ts +108 -49
- package/dist/index.js +114 -91
- package/dist/index.mjs +66 -59
- package/dist/motion/FlatList.d.mts +2 -1
- package/dist/motion/FlatList.d.ts +2 -1
- package/dist/motion/FlatList.js +5 -5
- package/dist/motion/FlatList.mjs +4 -4
- package/dist/motion/Image.d.mts +2 -1
- package/dist/motion/Image.d.ts +2 -1
- package/dist/motion/Image.js +5 -5
- package/dist/motion/Image.mjs +4 -4
- package/dist/motion/Pressable.d.mts +2 -1
- package/dist/motion/Pressable.d.ts +2 -1
- package/dist/motion/Pressable.js +5 -5
- package/dist/motion/Pressable.mjs +4 -4
- package/dist/motion/ScrollView.d.mts +2 -1
- package/dist/motion/ScrollView.d.ts +2 -1
- package/dist/motion/ScrollView.js +5 -5
- package/dist/motion/ScrollView.mjs +4 -4
- package/dist/motion/Text.d.mts +2 -1
- package/dist/motion/Text.d.ts +2 -1
- package/dist/motion/Text.js +5 -5
- package/dist/motion/Text.mjs +4 -4
- package/dist/motion/View.d.mts +2 -1
- package/dist/motion/View.d.ts +2 -1
- package/dist/motion/View.js +5 -5
- package/dist/motion/View.mjs +4 -4
- package/dist/touch/index.d.mts +5 -3
- package/dist/touch/index.d.ts +5 -3
- package/dist/touch/index.js +16 -31
- package/dist/touch/index.mjs +11 -29
- package/dist/{types-DyJpG64F.d.mts → types-C5Zbs9MC.d.mts} +22 -4
- package/dist/{types-DyJpG64F.d.ts → types-C5Zbs9MC.d.ts} +22 -4
- package/dist/{useGesture-DbH46EBp.d.ts → useGesture-Be0ZhCWj.d.ts} +1 -1
- package/dist/{useGesture-CnZQnYHH.d.mts → useGesture-Ve8EGjTr.d.mts} +1 -1
- package/dist/useTranslateStyle-BDrOD97O.d.mts +58 -0
- package/dist/useTranslateStyle-BDrOD97O.d.ts +58 -0
- package/jest-preset.js +31 -2
- package/llms.txt +2 -1
- package/package.json +15 -12
- package/src/config/namedTransitions.ts +7 -9
- package/src/gestureLayer/index.ts +3 -0
- package/src/gestureLayer/useGestureLayer.ts +6 -9
- package/src/gestures/focusVisibility.ts +19 -17
- package/src/gestures/index.ts +1 -1
- package/src/index.ts +12 -0
- package/src/internal/boxShadow.ts +24 -2
- package/src/layout/resolveLayout.ts +4 -1
- package/src/layout/useSharedLayout.ts +2 -1
- package/src/motion/createMotionComponent.tsx +92 -44
- package/src/presence/Presence.tsx +5 -7
- package/src/touch/applyBounds.ts +30 -0
- package/src/touch/index.ts +1 -0
- package/src/touch/useTouchDrag.ts +18 -46
- package/src/transitions/constants.ts +14 -0
- package/src/transitions/easing.ts +23 -11
- package/src/transitions/index.ts +5 -0
- package/src/transitions/keys.ts +0 -2
- package/src/transitions/resolve.ts +86 -11
- package/src/transitions/resolveSequence.ts +45 -3
- package/src/transitions/runtime.ts +4 -7
- package/src/transitions/spring.ts +7 -2
- package/src/types.ts +23 -3
- package/src/values/extrapolation.ts +21 -0
- package/src/values/index.ts +3 -0
- package/src/values/useColorCascade.ts +3 -3
- package/src/values/useColorTransition.ts +17 -2
- package/src/values/useGesture.ts +9 -2
- package/src/values/useInterpolatedStyle.ts +13 -13
- package/src/values/useShadow.ts +36 -9
- package/src/values/useSpring.ts +34 -25
- package/src/values/useTransform.ts +24 -20
- package/src/values/useTranslateStyle.ts +47 -0
- package/dist/chunk-24NUL23E.js +0 -8
- package/dist/chunk-4XBX7KOD.js +0 -8
- package/dist/chunk-Z5USXWTE.js +0 -8
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,98 @@ All notable changes to `@rootnative/inertia` are documented here. The format fol
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.0.11] - 2026-09-09
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **Breaking: the peer ranges are narrowed to the Expo SDK 57 band.** `react` is now `>=19.2.3 <20.0.0`, `react-native` is `>=0.83.0 <0.87.0`, `react-native-reanimated` is `>=4.5.0 <4.6.0`, and `react-native-worklets` is `>=0.10.0 <0.11.0`. The old ranges advertised a floor that npm rejects. `0.0.10` declares `react-native >=0.81.0` and `react-native-reanimated >=4.0.0`, and the newest Reanimated that range admits requires `react-native 0.83 - 0.87`, so a fresh install can fail with `ERESOLVE`. Nothing in this repository changed when that became true — Reanimated narrowed its own range after the release, which is why a range needs a gate that re-checks it and not only a review at the time it is written.
|
|
12
|
+
|
|
13
|
+
The band cannot be wider. Each peer range resolves on its own, so any set that admits both SDK 54 (`react-native 0.81` with Reanimated `4.1`) and SDK 57 (`react-native 0.86` with Reanimated `4.5`) also admits `react-native 0.81` with Reanimated `4.6`, which cannot install. A range promises every combination it permits, not only the combinations that were tested.
|
|
14
|
+
|
|
15
|
+
A new `compat.yml` workflow now packs the release candidate and installs it into every Expo SDK fixture in `rootnative/sdk-compat` before the publish, with `enforce-range: true`. The test suite in this repository says nothing about what a consumer can install, and that gap is what let the `0.0.10` range ship.
|
|
16
|
+
|
|
17
|
+
Migration: move the app to Expo SDK 57 (React `19.2.3`, React Native `0.86.3`, Reanimated `4.5.1`, Worklets `0.10.1`). A project on SDK 54 stays on `0.0.10`. The dev pins and the example app moved to the same band, so the package now develops against the runtime it declares. `sdk-compat` records SDK 54 as a documented boundary, not as a failure.
|
|
18
|
+
|
|
19
|
+
- **`@rootnative/inertia/jest-preset` needs `@react-native/jest-preset` in the consuming project.** React Native `0.86` moved its Jest preset into that package and left `react-native/jest-preset` as a shim that re-exports it. The shim declares the new package as an **optional** peer, and no package manager installs an optional peer, so the shim throws a migration error for every consumer who did not add the package by hand. The preset now resolves `@react-native/jest-preset` directly and throws a named error that says what to install, so the failure states this package's requirement instead of surfacing as a React Native migration message. Add it as a devDependency at the version that matches your `react-native`. The docs testing page carries the install step and the new `preset` spelling.
|
|
20
|
+
|
|
21
|
+
The package is deliberately **not** a peer dependency here. React Native pins it to an exact version — `0.86.3` requires exactly `@react-native/jest-preset@0.86.3` — so any range declared in this package would advertise versions that cannot install, and the `sdk-compat` range audit fails on it. An exact pin would break on every React Native patch. The version relationship belongs to `react-native`, which already declares it.
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- **Three style types are exported: `ColorStyle`, `TranslateStyle` and `ShadowStyle`.** They are what `useColorTransition`, `useColorCascade`, `useTranslateStyle` and `useShadow` return. Each hook was public while the type of the style object it hands back was not, so a consumer could not name the value in a typed variable, a prop, or a wrapper hook's signature without rebuilding the type by hand.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- **A keyframe array no longer crashes the whole render under reduced motion.** `withSequence` writes a `finished` flag onto every argument it is handed. Reduced motion collapses each step to `no-animation`, and `no-animation` resolves to the bare target rather than an animation object, so `animate={{ translateY: [0, -8, 0] }}` reached `withSequence(0, -8, 0)` and threw `TypeError: Cannot create property 'finished' on number` from inside render. React unmounted the tree, so any consumer using the documented keyframe form — the shape in this package's own `llms.txt` — served a blank page to every visitor with reduce-motion switched on at the OS level. The resolver now settles the property on the last keyframe instead of building a sequence, which is where the sequence would have ended; `repeat` and `delay` were already dropped for `no-animation`, so nothing is left pending. A per-step `{ type: 'no-animation' }` inside a sequence that otherwise animates hit the same throw and now becomes a zero-length timing, keeping its slot in the order.
|
|
30
|
+
|
|
31
|
+
Found from a consumer, not from CI, and the reason is worth keeping: **the Jest mock cannot observe this class of bug.** Its `withSequence` is `(...args) => args[args.length - 1]` and its `withTiming` / `withSpring` return their target, so every step looks like a bare value whether the gate is on or off — the existing reduced-motion sequence tests passed against the crashing code. The regression tests therefore assert the one thing that stays observable under the mock: a sequence under the gate never reaches `withSequence` at all. For the same reason the gate is now detected from the resolved config (`no-animation` base), never by inspecting the shape of a resolved step.
|
|
32
|
+
|
|
33
|
+
## [0.0.10] - 2026-09-06
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- **`react-native-worklets` is a required peer, not an optional one.** Core imports `isWorkletFunction` from it without a guard in `transitions/easing.ts` and `values/useTransform.ts`, so the package never worked without it. The `optional: true` flag in `peerDependenciesMeta` is removed. A package manager now warns when the peer is missing instead of staying silent. The installation docs already list it as a peer, so no install step changes. Core also declares it as a devDependency, pinned to `~0.5.1` next to Reanimated `~4.1.1`; before, the workspace typecheck passed only because the example app hoisted it. Found by the `1.0.0` readiness audit (2026-09-05).
|
|
38
|
+
|
|
39
|
+
- **`@testing-library/react-native` is declared as an optional peer (`>=13.0.0`).** The `@rootnative/inertia/testing` entry point imports its `render`, but no field in `package.json` said so. A consumer without the library got a bare module-not-found error from that subpath. It is optional because only that entry point needs it. The docs testing page now names the requirement. Found by the `1.0.0` readiness audit (2026-09-05).
|
|
40
|
+
|
|
41
|
+
### Removed
|
|
42
|
+
|
|
43
|
+
- **Breaking: `restSpeedThreshold` and `restDisplacementThreshold` are removed from `SpringTransition`.** Both keys were silently inert on every release since `0.0.1`. They are Reanimated 3 names: Reanimated 4 settles springs on an internal `energyThreshold`, its `SpringConfig` type does not have either key, and its runtime never reads them. Inertia forwarded both into `withSpring`, where they were ignored — a documented public key that did nothing, which is the defect class the `0.0.5` type narrowing exists to prevent. Found by the pre-`1.0.0` review of the transition vocabulary.
|
|
44
|
+
|
|
45
|
+
Migration: delete the two keys from any spring config. Their removal cannot change animation behavior, because they never had any. If a settle knob is needed later, Reanimated 4's `overshootClamping` / `energyThreshold` are the candidates, as a non-breaking addition.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- **`repeat: 0` (and any count below 1) runs the animation once instead of forever.** Reanimated's `withRepeat` reads a count of `0` or less as endless, and the resolver forwarded the count as given, so `repeat: 0` — the natural way to write "do not repeat" from a variable — looped without end. Counts below `1` are now treated as "run once", warn once in dev, and are not forwarded. `onAnimationEnd`'s iteration counter and `<Presence>`'s settle gate use the same rule through the new `repeatIterationsOf` helper, so the three cannot disagree. Found by the `1.0.0` readiness audit (2026-09-05), Phase 3.
|
|
50
|
+
|
|
51
|
+
- **`repeat.alternate` is no longer forwarded for a sequence, and an explicit `alternate: true` on a sequence warns.** Reanimated's `reverse` flag only swaps the wrapped animation's target, which a `withSequence` ignores — every pass restarts at the first step — so the flag never alternated a sequence. The resolver now passes `reverse: false` for sequences, the `RepeatConfig` docs state the limit, and the sequences page shows the working form: write the reverse steps into the sequence. Found by the `1.0.0` readiness audit (2026-09-05), Phase 3.
|
|
52
|
+
|
|
53
|
+
- **`<Presence>` no longer releases an exiting child early when its animation run is replaced mid-exit.** The settle counter that gates `safeToRemove` counted every callback, including the `finished: false` callbacks Reanimated fires for animations that a later effect run replaced. If the value-driving effect re-ran during an exit (a controller change, a reduce-motion flip), the replaced run drained its own counter and unmounted the child while the new run was still animating. Each run is now a token: the effect's cleanup marks the run done, and a superseded run's callbacks are ignored. Pinned by `phase3-audit.test.tsx`, which fails against the previous implementation. Found by the `1.0.0` readiness audit (2026-09-05), Phase 3.
|
|
54
|
+
|
|
55
|
+
- **An `onAnimationEnd` handler added after mount is wired without waiting for the next animation change.** The callback factory skips callback installation when there is no handler and nothing to settle. A handler that went from `undefined` to defined on a later render was read through a ref, but no callback existed to read it, so it stayed silent until `animate` or `transition` changed. The effect is now also keyed on the handler's presence (not its identity — a changing handler still costs nothing). Found by the `1.0.0` readiness audit (2026-09-05), Phase 3.
|
|
56
|
+
|
|
57
|
+
- **`useSpring` and `useBooleanSpring` honour reduced motion.** Under `<MotionConfig reducedMotion>` both hooks assign the target to the output directly, with no spring, on the plain-number path and the shared-value path. They were the only value-layer hooks that ignored the setting. Found by the `1.0.0` readiness audit (2026-09-05), Phase 3.
|
|
58
|
+
|
|
59
|
+
- **Dev warnings fire once per misconfiguration, not once per render.** The unknown-variant warning (`animate="open"` with no matching variant), the unknown named-transition warning, the `<Presence>` keyless-child warning, the `useSpring` spring-only warning, and the `useInterpolatedStyle` range-length warning all went through a bare `console.warn` in render or in a per-render resolver, so a single mistake flooded the console. All now use `warnOnce`, keyed per distinct misconfiguration. `useSwipe`'s decay `releaseTransition` warning in `-gestures` is guarded the same way. Found by the `1.0.0` readiness audit (2026-09-05), Phase 3.
|
|
60
|
+
|
|
61
|
+
- **`useTouchDrag` no longer rebuilds its `PanResponder` when an inline callback changes identity.** `onDragStart` / `onDragEnd` / `onRelease` were dependencies of the responder memo, so `onRelease={(e) => ...}` written inline rebuilt the responder on every render. The callbacks are now read through a ref at call time; the responder depends only on the axis, constraint, and elastic scalars, and it always calls the latest callback. Found by the `1.0.0` readiness audit (2026-09-05), Phase 3.
|
|
62
|
+
|
|
63
|
+
- **`useShadow`, `useTransform`, and `useGestureLayer` no longer rebuild their worklet for inline literals.** `useShadow` re-parsed the `boxShadow` pairs on every render, `useTransform` captured a fresh `inputRange` / `outputRange` array, and `useGestureLayer` keyed its per-key table on the identity of each `states` layer object. Each render with inline literals — the documented way to call all three — produced a new UI-thread closure. All three now memoise on a structural signature (`stableSig`). Found by the `1.0.0` readiness audit (2026-09-05), Phase 3.
|
|
64
|
+
|
|
65
|
+
- **`sideEffects: false` is now true.** The `focusVisible` input-modality tracker attached its web `document` listeners at import time, which is a side effect the package declared it did not have. The listeners now attach from a mount effect of the first component that tracks `focusVisible` (the `gesture` prop or `useGesture`). Mount time is early enough: the click that focuses an element lands on an element that is already mounted, so the preceding `mousedown` is observed. `installFocusVisibility` is exported from `./gestures` for the same purpose. Found by the `1.0.0` readiness audit (2026-09-05), Phase 3.
|
|
66
|
+
|
|
67
|
+
- **A key added to `animate` after mount now animates on native.** The `useAnimatedStyle` worklet read the active key set through plain `useRef` objects. `react-native-worklets` clones a captured plain object once, when the worklet is first serialised, and in dev it freezes the original, so later writes to `.current` were dropped with a `Tried to modify key 'current'` warning. The worklet only ever saw the mount-time set: a parent that changed `animate={{ opacity: 1 }}` to `animate={{ opacity: 1, translateX: 120 }}` drove the `translateX` shared value, but the element did not move. The set now crosses to the UI thread through a shared value that is rewritten once per growth. The Jest mock runs the worklet on the JS thread and never showed the defect; it was confirmed and fixed against an Android emulator. Found by the `1.0.0` readiness audit (2026-09-05).
|
|
68
|
+
|
|
69
|
+
- **`useSpring` follows a swapped `SharedValue` target.** The UI-thread reaction listed only the target kind and the spring config as dependencies, and an explicit list replaces Reanimated's closure-derived one. A caller that changed which shared value drives the spring kept the reaction wired to the first one. The current target is now in the list; a plain-number target maps to `null` there, so a changing number does not re-register the inert reaction. Found by the `1.0.0` readiness audit (2026-09-05).
|
|
70
|
+
|
|
71
|
+
### Added
|
|
72
|
+
|
|
73
|
+
- **`useTranslateStyle(x, y)` is exported from the root barrel.** The `transform: [{ translateX }, { translateY }]` animated style that `useTouchDrag`, `useDrag`, `usePan`, and `useSwipe` all return; the four hooks now share this one implementation. Use it directly when a custom gesture owns its own translation values. Documented in `docs/docs/api/hooks.md`. Found by the `1.0.0` readiness audit (2026-09-05), Phase 3.
|
|
74
|
+
|
|
75
|
+
- **`applyBounds(value, min, max, elastic)` is exported from the root barrel.** The worklet-safe clamp with optional rubber-band overshoot that `useTouchDrag` and `-gestures`' `useDrag` apply their `constraints` and `elastic` options through; the two hooks carried identical private copies. Documented in `docs/docs/api/hooks.md`. Found by the `1.0.0` readiness audit (2026-09-05), Phase 3.
|
|
76
|
+
|
|
77
|
+
- **`isTopLevelTransition(value)` is exported from the root barrel.** It is the structural discriminator the factory uses to tell a single `TransitionConfig` from a per-property map: every key must be a known config field. Custom animated components that accept both shapes need the same test; `'type' in transition` is wrong because `SpringTransition.type` is optional. The three adapter packages had that bug and now use this export; see `docs/docs/api/transition-utilities.md`.
|
|
78
|
+
|
|
79
|
+
- **`stableSig(value)` is exported from the root barrel.** It is the order-independent structural signature the factory already keys its `transition` effects on. Custom animated components built on `resolveTransition` need the same thing: an inline `transition` literal is a new object every render, so an effect keyed on its identity restarts the animation each time the parent renders. The three adapter packages now use it for exactly that; see `docs/docs/api/transition-utilities.md`.
|
|
80
|
+
|
|
81
|
+
- **Three types that already appeared in exported signatures are now exported.** `GestureLayerTransitions` (the per-layer form of a gesture `transition`; from the root and from `@rootnative/inertia/gesture-layer`), `CallbackFactory` (the third parameter of `resolveAnimatableValue`), and `MotionComponentProps` (the props of a `Motion.*` primitive for a given component, for typing a wrapper). Pinned by `__type-tests__/public-types.test-d.tsx`. Found by the `1.0.0` readiness audit (2026-09-05).
|
|
82
|
+
|
|
83
|
+
- **Dev warning when `controller` and `animate` are set on the same primitive.** The controller drives the animation and `animate` is ignored; that precedence was documented but silent. It now warns once in dev.
|
|
84
|
+
|
|
85
|
+
### Documentation
|
|
86
|
+
|
|
87
|
+
- `useVariants` documents that the controller pins the variants map from the first render, and that later renders passing a different map object do not re-key it.
|
|
88
|
+
|
|
89
|
+
- `RepeatConfig` and the sequences page document the count rule (total iterations, minimum `1`) and the `alternate` limit for sequences. `useSpring` documents that it honours reduced motion.
|
|
90
|
+
|
|
91
|
+
### Internal
|
|
92
|
+
|
|
93
|
+
- The adapter peer range on `@rootnative/inertia` now has an upper bound: `>=<version> <next-minor>` instead of `>=<version>`. Pre-`1.0.0`, a minor bump is where a breaking change lands, so an adapter at `0.0.x` must not accept core `0.1.0`. `scripts/check-versions.mjs` check 2 and both release workflows compute the same bound. Found by the `1.0.0` readiness audit (2026-09-05), Phase 3.
|
|
94
|
+
|
|
95
|
+
- Duplicated code consolidated: `mapExtrapolation` (was in `useTransform` and `useInterpolatedStyle`) lives in `values/extrapolation.ts`; `DEFAULT_TIMING_DURATION` (250 ms, was declared in both `resolve.ts` and `runtime.ts`) and `DEFAULT_LAYOUT_DURATION` (300 ms, was a literal in two layout files) live in `transitions/constants.ts`; `buildReleaseAnimation` unwraps an `EasingFunctionFactory` through the same worklet helper `ensureWorkletEasing` uses (`unwrapEasingFactory`). Found by the `1.0.0` readiness audit (2026-09-05), Phase 3.
|
|
96
|
+
|
|
97
|
+
- Tests added for the gaps the audit listed: `useVariants().transitionTo` driving a primitive, the `Motion.Image` runtime path, `useDrag`'s `onDragStart` and decay branch, `usePan`'s `withDecay` config, `MotionLinearGradient`'s locations-presence throw, and `MotionPath`'s `initial.d` mismatch throw.
|
|
98
|
+
|
|
7
99
|
## [0.0.9] - 2026-08-22
|
|
8
100
|
|
|
9
101
|
### Changed
|
|
@@ -285,7 +377,9 @@ Initial alpha publish. The full initial surface is in place; APIs are still subj
|
|
|
285
377
|
- SVG path morphing, gradient interpolation, and shared-element transitions across screens are out of scope until `0.2.x` / `1.x` per the roadmap.
|
|
286
378
|
- `react-native-gesture-handler` integration (drag, pan, swipe sub-states) lands in `0.2` via the optional `@rootnative/inertia-gestures` adapter.
|
|
287
379
|
|
|
288
|
-
[unreleased]: https://github.com/rootnative/inertia/compare/core+gestures+gradients+svg@0.0.
|
|
380
|
+
[unreleased]: https://github.com/rootnative/inertia/compare/core+gestures+gradients+svg@0.0.11...HEAD
|
|
381
|
+
[0.0.11]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.11
|
|
382
|
+
[0.0.10]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.10
|
|
289
383
|
[0.0.9]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.9
|
|
290
384
|
[0.0.8]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.8
|
|
291
385
|
[0.0.7]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.7
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
Declarative animation primitives for React Native, built as a thin wrapper around [`react-native-reanimated`](https://docs.swmansion.com/react-native-reanimated/). Inspired by Framer Motion (web) and react-spring (cross-platform).
|
|
14
14
|
|
|
15
|
-
> **Status:** `0.0.
|
|
15
|
+
> **Status:** `0.0.11` — stable. Pre-`1.0.0` minor versions may break — see the [CHANGELOG](https://github.com/rootnative/inertia/blob/main/packages/core/CHANGELOG.md).
|
|
16
16
|
|
|
17
17
|
## Install
|
|
18
18
|
|
|
@@ -22,7 +22,7 @@ pnpm add @rootnative/inertia react-native-reanimated
|
|
|
22
22
|
|
|
23
23
|
Then enable the [Reanimated Babel plugin](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/installation).
|
|
24
24
|
|
|
25
|
-
**Peer dependencies:** `react >=19.
|
|
25
|
+
**Peer dependencies:** `react >=19.2.3`, `react-native >=0.83.0 <0.87.0`, `react-native-reanimated >=4.5.0 <4.6.0`, `react-native-worklets >=0.10.0 <0.11.0`.
|
|
26
26
|
|
|
27
27
|
## Quick start
|
|
28
28
|
|
|
@@ -76,12 +76,12 @@ Both forms land at ~4.1–4.2 kB brotlied for a single primitive (peers excluded
|
|
|
76
76
|
|
|
77
77
|
## Transitions
|
|
78
78
|
|
|
79
|
-
| `type` | Public config keys
|
|
80
|
-
| -------------------- |
|
|
81
|
-
| `'spring'` (default) | `tension`, `friction`, `mass`, `velocity
|
|
82
|
-
| `'timing'` | `duration`, `easing`, `delay`
|
|
83
|
-
| `'decay'` | `velocity`, `deceleration`, `clamp`
|
|
84
|
-
| `'no-animation'` | —
|
|
79
|
+
| `type` | Public config keys | Maps to |
|
|
80
|
+
| -------------------- | ----------------------------------------- | ----------------------------------- |
|
|
81
|
+
| `'spring'` (default) | `tension`, `friction`, `mass`, `velocity` | `withSpring` |
|
|
82
|
+
| `'timing'` | `duration`, `easing`, `delay` | `withTiming` |
|
|
83
|
+
| `'decay'` | `velocity`, `deceleration`, `clamp` | `withDecay` |
|
|
84
|
+
| `'no-animation'` | — | direct assignment, no interpolation |
|
|
85
85
|
|
|
86
86
|
Plus, on any transition: `delay`, `repeat`. Per-property transitions take precedence over the top-level transition. Spring config uses **react-spring vocabulary** (`tension`/`friction`); Reanimated's raw `stiffness`/`damping` is never on the public surface.
|
|
87
87
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { stableSig, isTopLevelTransition } from './chunk-
|
|
1
|
+
import { stableSig, warnOnce, isTopLevelTransition } from './chunk-QIRKPDXA.mjs';
|
|
2
2
|
import { createContext, useContext, useMemo } from 'react';
|
|
3
3
|
import { useReducedMotion } from 'react-native-reanimated';
|
|
4
4
|
import { jsx } from 'react/jsx-runtime';
|
|
@@ -47,11 +47,10 @@ var UNKNOWN_NAME_FALLBACK = { type: "spring" };
|
|
|
47
47
|
function lookupNamedTransition(name, registry) {
|
|
48
48
|
const cfg = registry[name];
|
|
49
49
|
if (cfg) return cfg;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
50
|
+
warnOnce(
|
|
51
|
+
`unknown-transition:${name}`,
|
|
52
|
+
`[inertia] Unknown transition name "${name}" \u2014 falling back to the default spring. Register it on a provider: <MotionConfig transitions={{ '${name}': { ... } }}>.`
|
|
53
|
+
);
|
|
55
54
|
return UNKNOWN_NAME_FALLBACK;
|
|
56
55
|
}
|
|
57
56
|
function resolveNamedTransition(input, registry) {
|
|
@@ -84,7 +83,7 @@ function setKeyboard() {
|
|
|
84
83
|
function setPointer() {
|
|
85
84
|
modality = "pointer";
|
|
86
85
|
}
|
|
87
|
-
function
|
|
86
|
+
function installFocusVisibility() {
|
|
88
87
|
if (installed) return;
|
|
89
88
|
if (Platform.OS !== "web") return;
|
|
90
89
|
if (typeof document === "undefined") return;
|
|
@@ -94,11 +93,10 @@ function ensureInstalled() {
|
|
|
94
93
|
document.addEventListener("touchstart", setPointer, true);
|
|
95
94
|
installed = true;
|
|
96
95
|
}
|
|
97
|
-
ensureInstalled();
|
|
98
96
|
function isFocusVisible() {
|
|
99
97
|
if (Platform.OS !== "web") return true;
|
|
100
|
-
|
|
98
|
+
installFocusVisibility();
|
|
101
99
|
return modality === "keyboard";
|
|
102
100
|
}
|
|
103
101
|
|
|
104
|
-
export { MotionConfig, isFocusVisible, lookupNamedTransition, resolveNamedTransition, resolveNamedTransitionProp, useMotionConfig, useNamedTransitions, useShouldReduceMotion };
|
|
102
|
+
export { MotionConfig, installFocusVisibility, isFocusVisible, lookupNamedTransition, resolveNamedTransition, resolveNamedTransitionProp, useMotionConfig, useNamedTransitions, useShouldReduceMotion };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkHBURWGCZ_js = require('./chunk-HBURWGCZ.js');
|
|
4
4
|
var reactNative = require('react-native');
|
|
5
5
|
|
|
6
|
-
var MotionScrollView =
|
|
6
|
+
var MotionScrollView = chunkHBURWGCZ_js.createMotionComponent(reactNative.ScrollView);
|
|
7
7
|
|
|
8
8
|
exports.MotionScrollView = MotionScrollView;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useNamedTransitions, resolveNamedTransitionProp, lookupNamedTransition, useShouldReduceMotion, isFocusVisible } from './chunk-
|
|
2
|
-
import { warnOnce, stableSig, applyDelay, resolveAnimatableValue, isTopLevelTransition, resolveTransition, ensureWorkletEasing, springToReanimated, DEFAULT_SPRING } from './chunk-
|
|
1
|
+
import { useNamedTransitions, resolveNamedTransitionProp, lookupNamedTransition, useShouldReduceMotion, installFocusVisibility, isFocusVisible } from './chunk-2Q6JMDYZ.mjs';
|
|
2
|
+
import { warnOnce, stableSig, applyDelay, resolveAnimatableValue, isTopLevelTransition, resolveTransition, repeatIterationsOf, DEFAULT_LAYOUT_DURATION, ensureWorkletEasing, springToReanimated, DEFAULT_SPRING } from './chunk-QIRKPDXA.mjs';
|
|
3
3
|
import { createContext, useContext, useMemo, Children, isValidElement, useState, useRef, useCallback, forwardRef, useEffect } from 'react';
|
|
4
4
|
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
5
5
|
import Animated, { useSharedValue, cancelAnimation, useAnimatedStyle, interpolateColor, reanimatedVersion, runOnJS, LinearTransition, withSequence, withTiming, withSpring } from 'react-native-reanimated';
|
|
@@ -39,11 +39,10 @@ function Presence({ children }) {
|
|
|
39
39
|
Children.forEach(children, (child) => {
|
|
40
40
|
if (!isValidElement(child)) return;
|
|
41
41
|
if (child.key === null) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
42
|
+
warnOnce(
|
|
43
|
+
"presence-keyless-child",
|
|
44
|
+
"[inertia] <Presence> children must have a `key`. Skipping a keyless child."
|
|
45
|
+
);
|
|
47
46
|
return;
|
|
48
47
|
}
|
|
49
48
|
out.push(child);
|
|
@@ -261,7 +260,7 @@ function resolveBoxShadowInput(input) {
|
|
|
261
260
|
offsetY: coerceLength(layer.offsetY, "offsetY"),
|
|
262
261
|
blurRadius: coerceLength(layer.blurRadius, "blurRadius"),
|
|
263
262
|
spreadDistance: coerceLength(layer.spreadDistance, "spreadDistance"),
|
|
264
|
-
color: layer.color
|
|
263
|
+
color: coerceColor(layer.color),
|
|
265
264
|
inset: layer.inset ?? false
|
|
266
265
|
}));
|
|
267
266
|
}
|
|
@@ -292,6 +291,13 @@ function invisibleLayer(inset) {
|
|
|
292
291
|
inset
|
|
293
292
|
};
|
|
294
293
|
}
|
|
294
|
+
function coerceColor(value) {
|
|
295
|
+
if (value === void 0) return "black";
|
|
296
|
+
if (typeof value === "string") return value;
|
|
297
|
+
throw new Error(
|
|
298
|
+
`[inertia] boxShadow: color must be a colour string, got ${typeof value}. PlatformColor and DynamicColorIOS values cannot be interpolated \u2014 pass a string colour on an animated shadow.`
|
|
299
|
+
);
|
|
300
|
+
}
|
|
295
301
|
function coerceLength(value, field) {
|
|
296
302
|
if (value === void 0) return 0;
|
|
297
303
|
if (typeof value === "number") return value;
|
|
@@ -358,7 +364,9 @@ function resolveLayoutTransition(layout) {
|
|
|
358
364
|
const cfg = layout === true ? { type: "spring" } : layout;
|
|
359
365
|
if (cfg.type === "no-animation") return void 0;
|
|
360
366
|
if (cfg.type === "timing") {
|
|
361
|
-
let builder2 = LinearTransition.duration(
|
|
367
|
+
let builder2 = LinearTransition.duration(
|
|
368
|
+
cfg.duration ?? DEFAULT_LAYOUT_DURATION
|
|
369
|
+
);
|
|
362
370
|
const easing = ensureWorkletEasing(cfg.easing);
|
|
363
371
|
if (easing) builder2 = builder2.easing(easing);
|
|
364
372
|
if (cfg.delay) builder2 = builder2.delay(cfg.delay);
|
|
@@ -554,7 +562,7 @@ function useSharedLayout(options) {
|
|
|
554
562
|
}
|
|
555
563
|
function legBuilder(transition) {
|
|
556
564
|
if (transition?.type === "timing") {
|
|
557
|
-
const duration = transition.duration ??
|
|
565
|
+
const duration = transition.duration ?? DEFAULT_LAYOUT_DURATION;
|
|
558
566
|
return (from, to) => withSequence(
|
|
559
567
|
withTiming(from, { duration: 0 }),
|
|
560
568
|
withTiming(to, { duration })
|
|
@@ -974,11 +982,8 @@ function createMotionComponent(Component) {
|
|
|
974
982
|
}
|
|
975
983
|
}
|
|
976
984
|
}
|
|
977
|
-
const
|
|
978
|
-
const
|
|
979
|
-
const hasShadowOffsetRef = useRef(false);
|
|
980
|
-
const hasBoxShadowRef = useRef(false);
|
|
981
|
-
const prevActive = activeKeysRef.current;
|
|
985
|
+
const activeSetRef = useRef(null);
|
|
986
|
+
const prevActive = activeSetRef.current?.keys ?? null;
|
|
982
987
|
let grew = prevActive === null;
|
|
983
988
|
if (!grew && prevActive) {
|
|
984
989
|
for (const k of touched) {
|
|
@@ -991,30 +996,32 @@ function createMotionComponent(Component) {
|
|
|
991
996
|
if (grew) {
|
|
992
997
|
const merged = new Set(prevActive ?? []);
|
|
993
998
|
for (const k of touched) merged.add(k);
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
if (__DEV__ && hasBoxShadowRef.current && activeKeysRef.current.some(
|
|
1003
|
-
(k) => k !== "boxShadow" && k.startsWith("shadow")
|
|
1004
|
-
)) {
|
|
999
|
+
const keys = ALL_KEYS.filter((k) => merged.has(k));
|
|
1000
|
+
activeSetRef.current = {
|
|
1001
|
+
keys,
|
|
1002
|
+
hasTransform: keys.some((k) => TRANSFORM_KEY_SET.has(k)),
|
|
1003
|
+
hasShadowOffset: keys.some((k) => SHADOW_OFFSET_KEY_SET.has(k)),
|
|
1004
|
+
hasBoxShadow: keys.includes("boxShadow")
|
|
1005
|
+
};
|
|
1006
|
+
if (__DEV__ && activeSetRef.current.hasBoxShadow && keys.some((k) => k !== "boxShadow" && k.startsWith("shadow"))) {
|
|
1005
1007
|
warnOnce(
|
|
1006
1008
|
"boxShadow-with-native-shadow",
|
|
1007
1009
|
"[inertia] `boxShadow` is animated alongside the native `shadow*` keys on the same element, which applies two shadow systems at once. Pick one: `boxShadow` for the cross-platform CSS form, or the native `shadow*` keys."
|
|
1008
1010
|
);
|
|
1009
1011
|
}
|
|
1010
1012
|
}
|
|
1013
|
+
const activeKeys = activeSetRef.current.keys;
|
|
1014
|
+
const activeSetSV = useSharedValue(activeSetRef.current);
|
|
1015
|
+
const activeSet = activeSetRef.current;
|
|
1016
|
+
useEffect(() => {
|
|
1017
|
+
if (activeSetSV.value !== activeSet) activeSetSV.value = activeSet;
|
|
1018
|
+
}, [activeSetSV, activeSet]);
|
|
1011
1019
|
const drivenNow = /* @__PURE__ */ new Set();
|
|
1012
1020
|
collectTouchedKeys(drivenNow, animateRecord);
|
|
1013
1021
|
if (initialRecord) collectTouchedKeys(drivenNow, initialRecord);
|
|
1014
1022
|
if (isExiting && exitRecord) collectTouchedKeys(drivenNow, exitRecord);
|
|
1015
1023
|
const everDrivenRef = useRef(/* @__PURE__ */ new Set());
|
|
1016
1024
|
for (const k of drivenNow) everDrivenRef.current.add(k);
|
|
1017
|
-
const activeKeys = activeKeysRef.current;
|
|
1018
1025
|
const everDriven = everDrivenRef.current;
|
|
1019
1026
|
const styleResting = {};
|
|
1020
1027
|
let styleRestingShadow = null;
|
|
@@ -1072,7 +1079,7 @@ function createMotionComponent(Component) {
|
|
|
1072
1079
|
);
|
|
1073
1080
|
const styleRestingSig = stableSig(styleResting);
|
|
1074
1081
|
useEffect(() => {
|
|
1075
|
-
for (const key of
|
|
1082
|
+
for (const key of activeKeys) {
|
|
1076
1083
|
if (everDrivenRef.current.has(key)) continue;
|
|
1077
1084
|
sharedValues[key].value = styleResting[key] ?? DEFAULT_RESTING[key];
|
|
1078
1085
|
if (key === "boxShadow") {
|
|
@@ -1105,6 +1112,7 @@ function createMotionComponent(Component) {
|
|
|
1105
1112
|
const baseRecord = isExiting && exitRecord ? { ...animateRecord, ...exitRecord } : animateRecord;
|
|
1106
1113
|
const baseSig = stableSig(baseRecord) + (isExiting ? "|exit" : "") + (shouldReduceMotion ? "|rm" : "");
|
|
1107
1114
|
const transitionSig = stableSig(transition);
|
|
1115
|
+
const hasAnimationEnd = onAnimationEnd !== void 0;
|
|
1108
1116
|
const safeToRemoveRef = useRef(void 0);
|
|
1109
1117
|
safeToRemoveRef.current = presence?.safeToRemove;
|
|
1110
1118
|
useEffect(() => {
|
|
@@ -1191,7 +1199,10 @@ function createMotionComponent(Component) {
|
|
|
1191
1199
|
if (isExiting && pending === 0) {
|
|
1192
1200
|
safeToRemoveRef.current?.();
|
|
1193
1201
|
}
|
|
1194
|
-
|
|
1202
|
+
return () => {
|
|
1203
|
+
done = true;
|
|
1204
|
+
};
|
|
1205
|
+
}, [baseSig, transitionSig, hasAnimationEnd]);
|
|
1195
1206
|
useGestureLayerProgress(
|
|
1196
1207
|
pressedProgress,
|
|
1197
1208
|
pressed,
|
|
@@ -1232,7 +1243,7 @@ function createMotionComponent(Component) {
|
|
|
1232
1243
|
if (!hasLayoutId) return void 0;
|
|
1233
1244
|
const out = {};
|
|
1234
1245
|
let any = false;
|
|
1235
|
-
for (const key of
|
|
1246
|
+
for (const key of activeKeys) {
|
|
1236
1247
|
if (!SHARED_STYLE_KEY_SET.has(key)) continue;
|
|
1237
1248
|
const v = sharedValues[key].value;
|
|
1238
1249
|
if (typeof v !== "number" && typeof v !== "string") continue;
|
|
@@ -1252,10 +1263,11 @@ function createMotionComponent(Component) {
|
|
|
1252
1263
|
const flip = sharedLayout.flip;
|
|
1253
1264
|
const carry = sharedLayout.carry;
|
|
1254
1265
|
const animatedStyle = useAnimatedStyle(() => {
|
|
1255
|
-
const
|
|
1256
|
-
const
|
|
1257
|
-
const
|
|
1258
|
-
const
|
|
1266
|
+
const activeSet2 = activeSetSV.value;
|
|
1267
|
+
const activeKeys2 = activeSet2.keys;
|
|
1268
|
+
const hasTransform = activeSet2.hasTransform;
|
|
1269
|
+
const hasShadowOffset = activeSet2.hasShadowOffset;
|
|
1270
|
+
const hasBoxShadow = activeSet2.hasBoxShadow;
|
|
1259
1271
|
const out = {};
|
|
1260
1272
|
const transform = [];
|
|
1261
1273
|
let shadowOffsetW = 0;
|
|
@@ -1554,12 +1566,7 @@ function stepCountOf(v) {
|
|
|
1554
1566
|
}
|
|
1555
1567
|
function totalIterationsOf(cfg) {
|
|
1556
1568
|
if (!cfg || cfg.type === "no-animation" || cfg.type === "decay") return 1;
|
|
1557
|
-
|
|
1558
|
-
if (r === void 0) return 1;
|
|
1559
|
-
if (r === "infinite") return Number.POSITIVE_INFINITY;
|
|
1560
|
-
if (typeof r === "number") return r;
|
|
1561
|
-
if (r.count === "infinite") return Number.POSITIVE_INFINITY;
|
|
1562
|
-
return r.count;
|
|
1569
|
+
return repeatIterationsOf(cfg.repeat);
|
|
1563
1570
|
}
|
|
1564
1571
|
function reachesTerminalPhase(cfg) {
|
|
1565
1572
|
return Number.isFinite(totalIterationsOf(cfg));
|
|
@@ -1586,16 +1593,21 @@ function useControllerKey(controller) {
|
|
|
1586
1593
|
return controller?.current;
|
|
1587
1594
|
}
|
|
1588
1595
|
function resolveAnimateInput(animate, variants, controllerKey) {
|
|
1596
|
+
if (controllerKey !== void 0 && animate !== void 0) {
|
|
1597
|
+
warnOnce(
|
|
1598
|
+
"controller-and-animate",
|
|
1599
|
+
"[inertia] Both `controller` and `animate` are set on the same primitive. The controller drives the animation and `animate` is ignored \u2014 remove one of the two."
|
|
1600
|
+
);
|
|
1601
|
+
}
|
|
1589
1602
|
if (controllerKey !== void 0 && variants && controllerKey in variants) {
|
|
1590
1603
|
return variants[controllerKey];
|
|
1591
1604
|
}
|
|
1592
1605
|
if (typeof animate === "string") {
|
|
1593
1606
|
if (variants && animate in variants) return variants[animate];
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
}
|
|
1607
|
+
warnOnce(
|
|
1608
|
+
`missing-variant:${animate}`,
|
|
1609
|
+
`[inertia] animate="${animate}" but no matching variant. Did you forget to pass \`variants\`?`
|
|
1610
|
+
);
|
|
1599
1611
|
return void 0;
|
|
1600
1612
|
}
|
|
1601
1613
|
return animate;
|
|
@@ -1663,6 +1675,9 @@ function useGestureHandlers(gesture, rest, setPressed, setFocused, setFocusVisib
|
|
|
1663
1675
|
const hasFocused = gesture?.focused ? 1 : 0;
|
|
1664
1676
|
const hasFocusVisible = gesture?.focusVisible ? 1 : 0;
|
|
1665
1677
|
const hasHovered = gesture?.hovered ? 1 : 0;
|
|
1678
|
+
useEffect(() => {
|
|
1679
|
+
if (hasFocusVisible) installFocusVisibility();
|
|
1680
|
+
}, [hasFocusVisible]);
|
|
1666
1681
|
return useMemo(() => {
|
|
1667
1682
|
if (!gesture) return {};
|
|
1668
1683
|
const handlers = {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { useShouldReduceMotion, resolveNamedTransitionProp, useNamedTransitions, isFocusVisible } from './chunk-
|
|
2
|
-
import { resolveTransition, isTopLevelTransition } from './chunk-
|
|
3
|
-
import { useCallback, useMemo } from 'react';
|
|
1
|
+
import { useShouldReduceMotion, resolveNamedTransitionProp, useNamedTransitions, installFocusVisibility, isFocusVisible } from './chunk-2Q6JMDYZ.mjs';
|
|
2
|
+
import { resolveTransition, isTopLevelTransition } from './chunk-QIRKPDXA.mjs';
|
|
3
|
+
import { useEffect, useCallback, useMemo } from 'react';
|
|
4
4
|
import { useSharedValue } from 'react-native-reanimated';
|
|
5
5
|
|
|
6
6
|
function useGesture(transition) {
|
|
@@ -10,6 +10,9 @@ function useGesture(transition) {
|
|
|
10
10
|
const hovered = useSharedValue(0);
|
|
11
11
|
const shouldReduceMotion = useShouldReduceMotion();
|
|
12
12
|
const resolved = resolveNamedTransitionProp(transition, useNamedTransitions());
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
installFocusVisibility();
|
|
15
|
+
}, []);
|
|
13
16
|
const setLayer = useCallback(
|
|
14
17
|
(sv, layer, target) => {
|
|
15
18
|
const cfg = shouldReduceMotion ? { type: "no-animation" } : layerTransition(layer, resolved) ?? { type: "spring" };
|