@octane-xplat/motion 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -2
- package/UPSTREAM.md +284 -16
- package/dist/native/index.js +980 -112
- package/dist/native/octane-client-build.json +1 -1
- package/dist/web/index.js +1104 -166
- package/dist/web/octane-client-build.json +1 -1
- package/package.json +8 -3
- package/src/clock.ts +13 -1
- package/src/components.mobile.test.tsrx +58 -0
- package/src/components.tsrx +185 -39
- package/src/components.web.test.tsrx +234 -1
- package/src/controller.ts +122 -10
- package/src/delegation.test.ts +269 -0
- package/src/delegation.ts +29 -0
- package/src/delegation.web.test.ts +270 -0
- package/src/drag.mobile.test.ts +81 -0
- package/src/drag.test.ts +167 -0
- package/src/drag.ts +157 -0
- package/src/driver.ts +249 -0
- package/src/driver.web.ts +180 -0
- package/src/engine.test.ts +86 -0
- package/src/engine.ts +137 -15
- package/src/hooks.tsrx +53 -2
- package/src/host-types.ts +44 -1
- package/src/host.ts +39 -2
- package/src/host.web.ts +51 -10
- package/src/index.ts +18 -4
- package/src/index.web.ts +18 -4
- package/src/pan.ts +92 -0
- package/src/pan.web.ts +116 -0
- package/src/presence.tsrx +6 -7
- package/src/reduced-motion.ts +23 -4
- package/src/types.ts +99 -10
- package/src/value.ts +15 -5
- package/src/variants.test.ts +197 -0
- package/src/variants.ts +226 -0
- package/types/engine.d.ts +7 -1
- package/types/index.d.ts +22 -1
- package/types/types.d.ts +93 -10
- package/types/value.d.ts +4 -1
package/README.md
CHANGED
|
@@ -2,9 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
Declarative numeric motion for Octane UI on web, iOS, and Android. Use
|
|
4
4
|
`motion.View`, `motion.Row`, or `motion.Pressable` with `initial`, `animate`,
|
|
5
|
-
and `transition
|
|
5
|
+
and `transition`, or `drag` with numeric translation bounds. Bound motion values update hosts without rendering each frame.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```tsx
|
|
8
|
+
import { motion } from '@octane-xplat/motion'
|
|
9
|
+
import { Text } from '@octane-xplat/ui'
|
|
10
|
+
|
|
11
|
+
export function MovingCard() {
|
|
12
|
+
return (
|
|
13
|
+
<motion.View
|
|
14
|
+
initial={{ opacity: 0 }}
|
|
15
|
+
animate={{ opacity: 1 }}
|
|
16
|
+
transition={{ duration: 0.3 }}
|
|
17
|
+
drag="x"
|
|
18
|
+
dragConstraints={{ left: 0, right: 120 }}
|
|
19
|
+
>
|
|
20
|
+
<Text>Drag the packing card</Text>
|
|
21
|
+
</motion.View>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
See the [motion guide](../../docs/app/animation-gestures.md) and maintained
|
|
8
27
|
[MotionDemo](examples/MotionDemo.tsrx). Use the [PresenceDemo](examples/PresenceDemo.tsrx) for retained exits.
|
|
9
28
|
The [compatibility record](UPSTREAM.md)
|
|
10
29
|
defines the supported subset and differences from `@octanejs/motion`.
|
|
@@ -13,3 +32,13 @@ Build with `pnpm --filter @octane-xplat/motion build`; run DOM/engine tests with
|
|
|
13
32
|
`pnpm --filter @octane-xplat/motion test` and universal lifecycle tests with
|
|
14
33
|
`pnpm --filter @octane-xplat/motion exec vitest run --config vitest.native.config.mts`.
|
|
15
34
|
Native compilation and object-driver tests are not physical-device evidence.
|
|
35
|
+
|
|
36
|
+
Native consumers must install `@nativescript-community/gesturehandler` and call
|
|
37
|
+
its `install()` before creating the root. See [drag setup](../../docs/app/animation-gestures.md#drag-a-component). Web does not require this optional peer.
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
// bootstrap.mobile.ts — before Application.run or creating Page/Frame roots.
|
|
41
|
+
import { install } from '@nativescript-community/gesturehandler'
|
|
42
|
+
|
|
43
|
+
install()
|
|
44
|
+
```
|
package/UPSTREAM.md
CHANGED
|
@@ -16,6 +16,11 @@ The adapter is original code; it does not copy upstream's DOM host factory.
|
|
|
16
16
|
| Config and reduced motion | src/context.ts; tests/conformance/reducedMotionConfig.test.ts | Context inheritance; spring transforms settle immediately on live preference changes while opacity can keep animating (`components.web.test.tsrx`, `engine.test.ts`) |
|
|
17
17
|
| Exit lifecycle | src/index.ts; tests/conformance/exit.test.ts | Deliberate live-subtree retention on both leaves; presence.web.test.tsrx and native presence test |
|
|
18
18
|
| Retained hosts on native | Not a DOM binding concern | components.mobile.test.tsrx uses the universal object driver |
|
|
19
|
+
| Bounded drag | src/index.ts pointer-drag binding; upstream/src/gestures/drag | Numeric constraints, scalar elasticity, callbacks, JS velocity spring; drag.test.ts and web/native handler tests |
|
|
20
|
+
| Interaction targets | whileTap/whileFocus gesture bindings | Host-level gesture seam (PointerEvents/touch/focus); snapshot + restore; components.web.test.tsrx, motion probe |
|
|
21
|
+
| Variant labels and bounded orchestration | src/context.ts; src/index.ts variant inheritance/stagger | variants.test.ts, web/native compiled host tests; numeric resolution before Controller |
|
|
22
|
+
| Scoped imperative runs | useAnimate | Controller registry keyed on host nodes; `ref` accepts callback refs and `{current}` scopes |
|
|
23
|
+
| Custom host components | motion.create / motion.<tag> | `motion.create(Component)` wraps shared-UI leaves; DOM tag proxy excluded |
|
|
19
24
|
|
|
20
25
|
Source links: [Octane motion](https://github.com/octanejs/octane/tree/main/packages/motion),
|
|
21
26
|
[upstream ledger](https://github.com/octanejs/octane/blob/main/packages/motion/UPSTREAM.md),
|
|
@@ -23,8 +28,11 @@ Source links: [Octane motion](https://github.com/octanejs/octane/tree/main/packa
|
|
|
23
28
|
|
|
24
29
|
## Deliberate boundaries
|
|
25
30
|
|
|
26
|
-
- Numeric values only. No CSS strings, keyframe arrays,
|
|
27
|
-
|
|
31
|
+
- Numeric values only. No CSS strings, keyframe arrays, layout/layoutId,
|
|
32
|
+
`whileHover`/`whileInView`, or broad framework-neutral re-export. Bounded drag,
|
|
33
|
+
variants, `whileTap`, `whileFocus`, lifecycle callbacks
|
|
34
|
+
(`onAnimationStart`/`onAnimationComplete`/`onUpdate`), `motion.create`, and
|
|
35
|
+
`useAnimate` match the upstream prop names.
|
|
28
36
|
- Host APIs use shared UI names rather than DOM tags. Existing UI props remain
|
|
29
37
|
available; motion owns transform channels and opacity. Put pre-existing CSS
|
|
30
38
|
transforms on an outer container. Conflicting writers are errors.
|
|
@@ -33,14 +41,231 @@ Source links: [Octane motion](https://github.com/octanejs/octane/tree/main/packa
|
|
|
33
41
|
- MotionConfig defaults to `reducedMotion="never"`, matching the reference;
|
|
34
42
|
choose `user` for system preferences. Config does not cross separate roots and
|
|
35
43
|
does not alter imperative value.animate calls: consult useReducedMotion there.
|
|
36
|
-
- Timing uses a platform clock.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
- Timing uses a monotonic platform clock (`System.nanoTime`/`CADisplayLink`-derived
|
|
45
|
+
on native, `performance.now` on web) with NativeScript's vsync scheduler.
|
|
46
|
+
- The same generator runs on web/native. Declarative tweens delegate to the
|
|
47
|
+
platform animator (`UIViewPropertyAnimator` on iOS, `ViewPropertyAnimator`
|
|
48
|
+
on Android, WAAPI `element.animate` on web) with per-frame presentation
|
|
49
|
+
tracking so interruption hands off value and velocity to the JS engine;
|
|
50
|
+
springs, reduced-motion runs, and gesture-driven values always run on the
|
|
51
|
+
JS engine. The web driver animates a matched function-list transform
|
|
52
|
+
keyframe plus `opacity`; sampled matrices decompose back to channels with
|
|
53
|
+
rotate read modulo ±180° and scale signs folded per the matrix.
|
|
54
|
+
- Springs accept both the physical spec (stiffness/damping/mass/velocity) and
|
|
55
|
+
upstream's duration/bounce spec. Transitions support `repeat`/`repeatType`
|
|
56
|
+
(`loop`/`reverse`/`mirror`)/`repeatDelay` and per-channel overrides in the
|
|
57
|
+
`{x: {…}, default: {…}}` form. Default declarative transition is a 0.3-second
|
|
58
|
+
easeInOut tween. Targets are absolute; scale multiplies scaleX/scaleY.
|
|
59
|
+
Opacity is clamped to 0–1 at the host. Reduced transforms have no delay.
|
|
60
|
+
Non-bezier eases, springs, repeats, and per-key transitions refuse platform
|
|
61
|
+
delegation and run on the JS engine on all targets.
|
|
62
|
+
|
|
63
|
+
## Bounded declarative drag
|
|
64
|
+
|
|
65
|
+
Decision #94 widens the exclusions recorded in #92. `drag={true}` moves both
|
|
66
|
+
axes; `drag="x"`/`"y"` owns one axis. Numeric `dragConstraints` edges are
|
|
67
|
+
absolute translation limits in CSS pixels/DIP; omitted edges are unbounded.
|
|
68
|
+
Measured-ref constraints are rejected explicitly. `dragElastic` is a scalar
|
|
69
|
+
0–1 (default 0.35; false = 0, true = 0.35), with linear resistance outside
|
|
70
|
+
bounds. Per-edge elasticity objects are excluded.
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
import { motion } from '@octane-xplat/motion'
|
|
74
|
+
|
|
75
|
+
export function DraggableCard() {
|
|
76
|
+
return <motion.View drag="x" dragConstraints={{ left: 0, right: 120 }} dragElastic={0.2} />
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`onDragStart`, `onDrag`, and `onDragEnd` receive `(event, info)`, where info has
|
|
81
|
+
`point`, `delta`, `offset`, `velocity` (units/second), and `cancelled`. Point is
|
|
82
|
+
viewport/screen coordinates. Offset and delta describe pointer movement before
|
|
83
|
+
constraints. Start fires on activation after an 8-unit threshold, rather than
|
|
84
|
+
on pointer-down; unsuccessful pre-activation gestures emit no drag callbacks.
|
|
85
|
+
Cancellation emits one end callback and suppresses momentum. Disposal removes
|
|
86
|
+
input handlers and stops settlement without emitting an end callback.
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
// motion is imported above; callbacks report gesture movement before constraints.
|
|
90
|
+
export function DragEvents() {
|
|
91
|
+
return (
|
|
92
|
+
<motion.View
|
|
93
|
+
drag
|
|
94
|
+
onDragStart={(_event, info) => console.log(info.point)}
|
|
95
|
+
onDrag={(_event, info) => console.log(info.delta, info.offset)}
|
|
96
|
+
onDragEnd={(_event, info) => console.log(info.velocity, info.cancelled)}
|
|
97
|
+
/>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Release projects `current + velocity * 0.2`, clamps the destination, and uses a
|
|
103
|
+
JS spring (stiffness 200, damping 30). `dragMomentum={false}` keeps the current
|
|
104
|
+
position if in bounds, or springs back from elastic overflow. This is a bounded
|
|
105
|
+
spring settle, **not upstream's inertia/decay algorithm**. Hard constraints
|
|
106
|
+
(`dragElastic={false}`) clamp every spring sample before host/value notification.
|
|
107
|
+
Reduced motion snaps release settlement; pointer tracking remains live.
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
export function BoundedCard() {
|
|
111
|
+
return (
|
|
112
|
+
<motion.View
|
|
113
|
+
drag="x"
|
|
114
|
+
dragConstraints={{ left: 0, right: 120 }}
|
|
115
|
+
dragElastic={false}
|
|
116
|
+
dragMomentum={false}
|
|
117
|
+
/>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
A plain or spring-backed `style.x`/`style.y` MotionValue can receive drag writes;
|
|
123
|
+
live writes use `jump` so passive spring following cannot lag the pointer.
|
|
124
|
+
Drag axes reject competing `animate`, `whileTap`, or `whileFocus` targets;
|
|
125
|
+
`initial` seeds the position and `exit` can own it after interaction ends.
|
|
126
|
+
No ref measurement, dragControls, dragListener, direction lock, propagation,
|
|
127
|
+
snap-to-origin, dragTransition, onDragTransitionEnd, or layout projection is
|
|
128
|
+
implemented. `drag` axis selection is not `dragDirectionLock`.
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
import { motion, useMotionValue } from '@octane-xplat/motion'
|
|
132
|
+
|
|
133
|
+
export function LivePosition() {
|
|
134
|
+
const x = useMotionValue(0)
|
|
135
|
+
return <motion.View drag="x" style={{ x }} initial={{ x: 0 }} exit={{ x: -100 }} />
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Web uses host PointerEvents/capture and `touch-action: pan-y` for horizontal
|
|
140
|
+
drag, `pan-x` for vertical drag, or `none` for both. Native imports the optional
|
|
141
|
+
peer `@nativescript-community/gesturehandler` (pinned to 2.0.45); native consumers
|
|
142
|
+
must install it even when using only other motion APIs because the native
|
|
143
|
+
entry imports its adapter. Web consumers do not need it. Apps call `install()`
|
|
144
|
+
before creating their Page/Frame/root; **do not call `install(true)`**, which
|
|
145
|
+
would replace existing NS gesture observers. There is no raw-pan fallback.
|
|
146
|
+
Motion reserves handler tags from 700000000 upward within its single module
|
|
147
|
+
instance. Native axis activation/failure uses 8 DIP; 2.0.45's Android config
|
|
148
|
+
setters need explicit DIP→pixel conversion while payloads already return DIP.
|
|
149
|
+
On Android single-axis handlers disable the default radial slop trigger so
|
|
150
|
+
only the selected axis can activate. The plugin's Manager owns view-init/dispose attachment; motion removes its
|
|
151
|
+
state/touch listeners and detaches on cleanup.
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
// Native bootstrap .mobile.ts, before creating Page/Frame/root hosts.
|
|
155
|
+
import { install } from '@nativescript-community/gesturehandler'
|
|
156
|
+
|
|
157
|
+
install() // Preserve existing NativeScript gesture observers.
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
NativeViewGestureHandler is not needed for the bounded surface: motion attaches
|
|
161
|
+
PanGestureHandler directly to its host, with no simultaneous/waitFor graph.
|
|
162
|
+
Nested native control ownership and complex competing drags are deferred.
|
|
163
|
+
Threshold configuration is source/test evidence of arbitration policy; only
|
|
164
|
+
real OS input inside a ScrollView can establish runtime arbitration.
|
|
165
|
+
|
|
166
|
+
## Variants (decision #93)
|
|
167
|
+
|
|
168
|
+
`variants` maps labels to numeric targets with an optional `transition`, or to
|
|
169
|
+
`(custom) => target` resolvers. The host's `custom` is passed to its own resolver;
|
|
170
|
+
current values and velocities are not resolver arguments. `initial`, `animate`,
|
|
171
|
+
`exit`, `whileTap`, and `whileFocus` accept targets, labels, or label arrays.
|
|
172
|
+
Arrays merge targets left to right; later channels win. Missing labels are
|
|
173
|
+
ignored. The last defined variant transition replaces the host/config transition
|
|
174
|
+
for that run; otherwise the normal default applies. Per-key transitions remain
|
|
175
|
+
supported. Resolvers should be pure; they may be evaluated during validation.
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
export function LabelledMotion() {
|
|
179
|
+
return (
|
|
180
|
+
<motion.View
|
|
181
|
+
custom={80}
|
|
182
|
+
initial="hidden"
|
|
183
|
+
animate={['shown', 'offset']}
|
|
184
|
+
variants={{
|
|
185
|
+
hidden: { opacity: 0 },
|
|
186
|
+
shown: { opacity: 1 },
|
|
187
|
+
offset: (custom: number) => ({ x: custom, transition: { duration: 0.2 } }),
|
|
188
|
+
}}
|
|
189
|
+
/>
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Initial labels (including `initial={false}`) inherit through a root-local
|
|
195
|
+
context. Animate labels propagate to descendant motion hosts without their own
|
|
196
|
+
`animate`; each host resolves its own map and `custom`. Ordinary UI wrappers do
|
|
197
|
+
not break propagation. An explicit `animate` makes that host an independent
|
|
198
|
+
animation subtree. Direct target objects are not inherited. Both shared motion
|
|
199
|
+
hosts and `motion.create` provide the context.
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
import { View } from '@octane-xplat/ui'
|
|
203
|
+
import { motion } from '@octane-xplat/motion'
|
|
204
|
+
|
|
205
|
+
const CustomMotionView = motion.create(View)
|
|
206
|
+
export function InheritedMotion() {
|
|
207
|
+
return (
|
|
208
|
+
<CustomMotionView initial="hidden" animate="shown">
|
|
209
|
+
<motion.View variants={{ hidden: { opacity: 0 }, shown: { opacity: 1 } }} />
|
|
210
|
+
</CustomMotionView>
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
On label-driven **animate** runs, the parent's resolved transition supports
|
|
216
|
+
numeric non-negative `delayChildren` and `staggerChildren` in seconds, applied
|
|
217
|
+
in child registration order and added to the child's own channel delays.
|
|
218
|
+
`when: 'beforeChildren'` waits for successful parent completion;
|
|
219
|
+
`'afterChildren'` waits for all inherited children; omitting `when` runs both
|
|
220
|
+
concurrently. Nested runs wait for their own descendants. Replacement and
|
|
221
|
+
unmount invalidate queued phases. A child mounted after a run starts joins
|
|
222
|
+
once that run completes, with fresh child delay; it does not extend that run's
|
|
223
|
+
completion barrier. Changing a retained child's resolved target/custom starts
|
|
224
|
+
its own inherited run. Registration order does not track keyed visual reorders.
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
export function StaggeredCards() {
|
|
228
|
+
return (
|
|
229
|
+
<motion.View
|
|
230
|
+
animate="shown"
|
|
231
|
+
variants={{
|
|
232
|
+
shown: {
|
|
233
|
+
opacity: 1,
|
|
234
|
+
transition: { delayChildren: 0.1, staggerChildren: 0.05, when: 'beforeChildren' },
|
|
235
|
+
},
|
|
236
|
+
}}
|
|
237
|
+
>
|
|
238
|
+
<motion.View variants={{ shown: { x: 40 } }} />
|
|
239
|
+
<motion.View variants={{ shown: { x: 80 } }} />
|
|
240
|
+
</motion.View>
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Exit and interaction labels resolve locally: they do not propagate activation
|
|
246
|
+
or child timing. Presence continues waiting for each registered host's explicit
|
|
247
|
+
exit; specify `exit` on children that need one. Callback completion remains
|
|
248
|
+
per-host playback, rather than a tree completion event. Unsupported upstream
|
|
249
|
+
semantics include `inherit={false}`, dynamic delay functions, `staggerDirection`,
|
|
250
|
+
`stagger()` helpers, gesture priority blending, `transitionEnd`, resolver chains,
|
|
251
|
+
and inheritance across separate renderer roots. Resolved targets and ordinary
|
|
252
|
+
transitions enter Controller unchanged, retaining existing delegation gates.
|
|
253
|
+
|
|
254
|
+
```tsx
|
|
255
|
+
import { Presence, motion } from '@octane-xplat/motion'
|
|
256
|
+
|
|
257
|
+
export function ExitingCards({ present }: { present: boolean }) {
|
|
258
|
+
return (
|
|
259
|
+
<Presence present={present}>
|
|
260
|
+
<motion.View
|
|
261
|
+
exit="hidden"
|
|
262
|
+
variants={{ hidden: { opacity: 0 } }}
|
|
263
|
+
onAnimationComplete={() => console.log('This host completed')}
|
|
264
|
+
/>
|
|
265
|
+
</Presence>
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
```
|
|
44
269
|
|
|
45
270
|
## Presence divergence
|
|
46
271
|
|
|
@@ -51,12 +276,52 @@ reuse that path or claim its cleanup timing. Presence renders an explicit View
|
|
|
51
276
|
wrapper, blocks interaction during exit, and restores interaction on reversal.
|
|
52
277
|
An ancestor unmount always disposes immediately. Nested boundaries are independent.
|
|
53
278
|
|
|
279
|
+
```tsx
|
|
280
|
+
import { useState } from 'octane'
|
|
281
|
+
import { Presence, motion } from '@octane-xplat/motion'
|
|
282
|
+
import { Button, Text } from '@octane-xplat/ui'
|
|
283
|
+
|
|
284
|
+
export function RetainedCard() {
|
|
285
|
+
const [present, setPresent] = useState(true)
|
|
286
|
+
return (
|
|
287
|
+
<>
|
|
288
|
+
<Button onPress={() => setPresent(!present)}>Toggle card</Button>
|
|
289
|
+
<Presence present={present}>
|
|
290
|
+
<motion.View exit={{ opacity: 0 }}>
|
|
291
|
+
<Text>Trip card</Text>
|
|
292
|
+
</motion.View>
|
|
293
|
+
</Presence>
|
|
294
|
+
</>
|
|
295
|
+
)
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
54
299
|
## Verification limits
|
|
55
300
|
|
|
301
|
+
Web WAAPI delegation (2026-10-02): unit tests drive a fake `Animation` through
|
|
302
|
+
the real web adapter for keyframe shape, matrix/matrix3d decomposition with
|
|
303
|
+
transform-origin correction, cancel-before-sample ordering, mid-flight
|
|
304
|
+
value/velocity handoff, and bezier-only gating; the retained probe passes on
|
|
305
|
+
headless Chromium with delegation counters engaged (14 starts, 10 finishes, 4
|
|
306
|
+
cancellations, 0 fallbacks). DOM dispatch evidence only — compositor pacing is
|
|
307
|
+
not asserted.
|
|
308
|
+
|
|
309
|
+
Bounded drag (2026-10-02): the maintained probe passes 14 assertions on web
|
|
310
|
+
Chromium and 14 on the requested iOS simulator, including live writes, bounded
|
|
311
|
+
momentum, callbacks, and cancellation. Native input is plugin handler dispatch;
|
|
312
|
+
it does not prove OS hit-testing or drag-versus-scroll arbitration. Android drag
|
|
313
|
+
runtime remains unverified. Package unit/build/packed-consumer checks cover the
|
|
314
|
+
public types and shared numeric behavior.
|
|
315
|
+
The variants expansion passes 60 standard tests and 5 native object-driver
|
|
316
|
+
tests. The maintained probe passes 11 assertions on web Chromium and the iOS
|
|
317
|
+
simulator (2026-10-02), including inherited custom destinations and
|
|
318
|
+
parent-before-children stagger completion order; iOS platform delegation engages.
|
|
319
|
+
No Android runtime was available for this expansion.
|
|
320
|
+
|
|
56
321
|
Unit and DOM tests establish bounded behavior, not full Framer Motion parity.
|
|
57
322
|
Universal object-driver tests establish retention and lifecycle without an OS.
|
|
58
323
|
Partial device observations are recorded in the
|
|
59
|
-
[motion v1 validation matrix](../../docs/animation-notes.md). An isolated iOS
|
|
324
|
+
[motion v1 validation matrix](../../docs/notes/animation-notes.md). An isolated iOS
|
|
60
325
|
26.5 MotionProbe run verified pan completion/cancellation, Presence
|
|
61
326
|
focus/reversal/removal, and live Reduce Motion, including immediate transform
|
|
62
327
|
settlement. The API 35 Android emulator verified tween/spring completion,
|
|
@@ -86,9 +351,12 @@ Physical Android CPH2551 evidence covers app launch and target retarget only;
|
|
|
86
351
|
ADB sees the handset, but its keyguard is currently locked. The regular mobile app now builds and
|
|
87
352
|
mounts on the iOS 26.5 simulator using NativeScript's `NSObject.extend()` API
|
|
88
353
|
for the auth-session presentation delegate. The Android build also passes with
|
|
89
|
-
the iOS delegate behind the `NSObject` runtime guard.
|
|
90
|
-
|
|
91
|
-
|
|
354
|
+
the iOS delegate behind the `NSObject` runtime guard. The maintained
|
|
355
|
+
`examples/probes/motion.tsrx` case replaces the earlier temporary direct
|
|
356
|
+
MotionProbe entry (web Chromium and iOS simulator passes, 2026-10-02).
|
|
357
|
+
Physical input suppression during exit, Android gesture
|
|
92
358
|
cancellation/natural velocity, background/resume, and frame pacing remain
|
|
93
|
-
uncharacterized.
|
|
94
|
-
|
|
359
|
+
uncharacterized. On iOS the preference is observed through
|
|
360
|
+
`UIAccessibilityReduceMotionStatusDidChangeNotification`; Android still polls
|
|
361
|
+
`animator_duration_scale` at 500 ms while subscribed (plus
|
|
362
|
+
`ValueAnimator.areAnimatorsEnabled()` on API 26+) and refreshes on resume.
|