@unrulysystems/native-motion 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +55 -0
  4. package/android/build.gradle +24 -0
  5. package/android/src/main/AndroidManifest.xml +1 -0
  6. package/android/src/main/java/com/unrulysystems/nativemotion/GestureExclusionModule.kt +55 -0
  7. package/android/src/main/java/com/unrulysystems/nativemotion/NativeMotionPackage.kt +27 -0
  8. package/package.json +57 -0
  9. package/react-native.config.js +14 -0
  10. package/src/native/driver/instantWindow.ts +31 -0
  11. package/src/native/driver/strictModeReplay.ts +20 -0
  12. package/src/native/driver/uiLayoutEngine.ts +845 -0
  13. package/src/native/driver/uiLayoutGraph.ts +338 -0
  14. package/src/native/driver/uiValueChannel.ts +4640 -0
  15. package/src/native/driver/workletDriver.ts +3860 -0
  16. package/src/native/motion/AnimatePresence.tsx +1605 -0
  17. package/src/native/motion/LayoutGroup.tsx +163 -0
  18. package/src/native/motion/MotionConfig.tsx +400 -0
  19. package/src/native/motion/MotionRoot.tsx +250 -0
  20. package/src/native/motion/MotionView.tsx +6761 -0
  21. package/src/native/motion/addScaleCorrector.ts +97 -0
  22. package/src/native/motion/colorEndpointUiFeed.ts +32 -0
  23. package/src/native/motion/colorProjection.ts +39 -0
  24. package/src/native/motion/composeTransform.ts +271 -0
  25. package/src/native/motion/constraintMeasure.ts +157 -0
  26. package/src/native/motion/deferredPendingSnapshots.ts +25 -0
  27. package/src/native/motion/discreteProjection.ts +36 -0
  28. package/src/native/motion/dragAncestorPanContext.ts +28 -0
  29. package/src/native/motion/dragControls.ts +152 -0
  30. package/src/native/motion/dragGestureWiring.ts +1168 -0
  31. package/src/native/motion/dragHandoffBinding.ts +179 -0
  32. package/src/native/motion/dragHubStream.ts +756 -0
  33. package/src/native/motion/dragPropagationLock.ts +57 -0
  34. package/src/native/motion/driverValueChannel.ts +3288 -0
  35. package/src/native/motion/externalDragDriver.ts +214 -0
  36. package/src/native/motion/frameData.ts +31 -0
  37. package/src/native/motion/gestureBinding.ts +107 -0
  38. package/src/native/motion/gestureStateGate.ts +563 -0
  39. package/src/native/motion/gestureStateResolver.ts +286 -0
  40. package/src/native/motion/identityValueChannelControllerAdapter.ts +2043 -0
  41. package/src/native/motion/identityValueChannelControllerReconciliation.ts +176 -0
  42. package/src/native/motion/identityValueChannelLaneMarker.ts +14 -0
  43. package/src/native/motion/imperativeAnimate.ts +816 -0
  44. package/src/native/motion/keyframeTiming.ts +7 -0
  45. package/src/native/motion/layoutIdBinding.ts +261 -0
  46. package/src/native/motion/layoutIdFlightConfig.ts +51 -0
  47. package/src/native/motion/layoutProjection.ts +89 -0
  48. package/src/native/motion/layoutScroll.ts +204 -0
  49. package/src/native/motion/layoutTransition.ts +1302 -0
  50. package/src/native/motion/lengthLayoutContext.tsx +100 -0
  51. package/src/native/motion/lengthLayoutHost.ts +44 -0
  52. package/src/native/motion/mappedKeys.ts +184 -0
  53. package/src/native/motion/motionViewController.ts +3180 -0
  54. package/src/native/motion/nativeHostMarker.ts +22 -0
  55. package/src/native/motion/panSessionWiring.ts +127 -0
  56. package/src/native/motion/pathTransition.ts +101 -0
  57. package/src/native/motion/popLayout.ts +176 -0
  58. package/src/native/motion/presenceBinding.ts +382 -0
  59. package/src/native/motion/scaleCorrectorRegistry.ts +123 -0
  60. package/src/native/motion/scrollValues.ts +260 -0
  61. package/src/native/motion/serializablePayload.ts +151 -0
  62. package/src/native/motion/severity.ts +16 -0
  63. package/src/native/motion/shippedSurface.ts +843 -0
  64. package/src/native/motion/staticLengthGate.ts +86 -0
  65. package/src/native/motion/styleBaseGate.ts +72 -0
  66. package/src/native/motion/styleValueBinding.ts +657 -0
  67. package/src/native/motion/systemGestureExclusion.ts +61 -0
  68. package/src/native/motion/tapGestureWiring.ts +215 -0
  69. package/src/native/motion/transformOrder.ts +38 -0
  70. package/src/native/motion/transformStringBinding.ts +200 -0
  71. package/src/native/motion/transformTemplateGate.ts +45 -0
  72. package/src/native/motion/transitionGate.ts +435 -0
  73. package/src/native/motion/useAnimate.ts +52 -0
  74. package/src/native/motion/useCycle.ts +44 -0
  75. package/src/native/motion/useInstantTransition.ts +68 -0
  76. package/src/native/motion/useReducedMotion.ts +58 -0
  77. package/src/native/motion/useScroll.ts +162 -0
  78. package/src/native/motion/useViewportScroll.ts +29 -0
  79. package/src/native/motion/valueChannel.ts +7121 -0
  80. package/src/native/motion/valueHooks.ts +901 -0
  81. package/src/native/motion/variantChildRegistry.ts +67 -0
  82. package/src/native/motion/variantContext.tsx +172 -0
  83. package/src/native/motion/variantProps.ts +598 -0
  84. package/src/native.ts +159 -0
  85. package/src/verification/harnessMetrics.ts +78 -0
  86. package/src/verification/probe/LayoutIdentityWorkletProbe.tsx +251 -0
  87. package/src/verification/probe/WorkletParityProbe.tsx +162 -0
  88. package/src/verification/probe/layoutIdentityProbeEngine.ts +429 -0
  89. package/src/verification/screens/ArcPathChecksScreen.tsx +273 -0
  90. package/src/verification/screens/BooleanAnimateChecksScreen.tsx +358 -0
  91. package/src/verification/screens/ChoreographyGalleryScreen.tsx +1010 -0
  92. package/src/verification/screens/ColorBindingChecksScreen.tsx +568 -0
  93. package/src/verification/screens/CompletionChecksScreen.tsx +294 -0
  94. package/src/verification/screens/ConformanceScreen.tsx +573 -0
  95. package/src/verification/screens/ContentionProbeScreen.tsx +224 -0
  96. package/src/verification/screens/DriverSmokeScreen.tsx +99 -0
  97. package/src/verification/screens/DurationOnlyTweenChecksScreen.tsx +294 -0
  98. package/src/verification/screens/DynamicDragConfigChecksScreen.tsx +237 -0
  99. package/src/verification/screens/FrameDataChecksScreen.tsx +252 -0
  100. package/src/verification/screens/GestureChecksScreen.tsx +685 -0
  101. package/src/verification/screens/InstantTransitionChecksScreen.tsx +839 -0
  102. package/src/verification/screens/LayoutAnimationStartChecksScreen.tsx +648 -0
  103. package/src/verification/screens/LayoutChecksScreen.tsx +822 -0
  104. package/src/verification/screens/LayoutCommitSpikeScreen.tsx +142 -0
  105. package/src/verification/screens/MotionViewChecksScreen.tsx +930 -0
  106. package/src/verification/screens/PresenceChecksScreen.tsx +614 -0
  107. package/src/verification/screens/ReducedMotionChecksScreen.tsx +614 -0
  108. package/src/verification/screens/RestThresholdChecksScreen.tsx +634 -0
  109. package/src/verification/screens/ScaleCorrectorChecksScreen.tsx +425 -0
  110. package/src/verification/screens/SharedLayoutContinuityChecksScreen.tsx +338 -0
  111. package/src/verification/screens/SharedLayoutCrossfadeChecksScreen.tsx +425 -0
  112. package/src/verification/screens/TransitionDefaultSelectionChecksScreen.tsx +514 -0
  113. package/src/verification/screens/ViewportScrollAliasChecksScreen.tsx +303 -0
  114. package/src/verification/screens/conformanceBanner.ts +21 -0
  115. package/src/verification/screens/proofConsoleTap.ts +18 -0
  116. package/src/verification.ts +47 -0
  117. package/src/web.ts +98 -0
@@ -0,0 +1,214 @@
1
+ // R15 D5 (REQ-API-046 / REQ-GESTURE-030): redirect drag session live reads, writes, and release
2
+ // commands to consumer-supplied MotionValues (`_dragX` / `_dragY`) instead of the host's own
3
+ // axis channels. The element's transform is never written by the session on an external axis
4
+ // (the pin's getAxisMotionValue branch).
5
+
6
+ import {
7
+ GESTURE_RELEASE_SPRING,
8
+ inertiaGenerator,
9
+ resolveSpringGenerator,
10
+ type DragAxis,
11
+ type Driver,
12
+ type ElementHandle,
13
+ type InertiaTransition,
14
+ type MotionValue,
15
+ type PropTransition,
16
+ type SpringConfig,
17
+ } from '@unrulysystems/native-motion-core'
18
+ import { resolveDefaultValueChannel } from './valueHooks'
19
+
20
+ export type ExternalDragValues = Partial<Record<DragAxis, MotionValue<number>>>
21
+
22
+ function springConfigFromRelease(transition: PropTransition): SpringConfig {
23
+ if (transition.type === 'spring') {
24
+ return {
25
+ stiffness: transition.stiffness ?? GESTURE_RELEASE_SPRING.stiffness,
26
+ damping: transition.damping ?? GESTURE_RELEASE_SPRING.damping,
27
+ }
28
+ }
29
+ return { ...GESTURE_RELEASE_SPRING }
30
+ }
31
+
32
+ /**
33
+ * R16 (REQ-GESTURE-023/-030, review major 1): the free-drag release's two-phase inertia
34
+ * generator on an external MotionValue — host parity with the driver's `kind:'inertia'`
35
+ * command. The descriptor's physics are used verbatim; the generator recomputes its ideal from
36
+ * the seed and owns the walls (an out-of-bounds origin boundary-springs from rest — it is
37
+ * never jumped to the target synchronously, the pin's t=0 latch). An MV the default graph
38
+ * cannot animate (a hostile/test double) is a consumer error and FAILS LOUD (severity law,
39
+ * review r2 major 1) — never suppressed into the synchronous jump this path forbids.
40
+ */
41
+ function inertiaReleaseExternal(
42
+ value: MotionValue<number>,
43
+ from: number,
44
+ velocity: number,
45
+ transition: InertiaTransition,
46
+ ): void {
47
+ const factory = inertiaGenerator({
48
+ power: transition.power,
49
+ timeConstant: transition.timeConstant,
50
+ bounceStiffness: transition.bounceStiffness,
51
+ bounceDamping: transition.bounceDamping,
52
+ restDelta: transition.restDelta,
53
+ restSpeed: transition.restSpeed,
54
+ ...(transition.min !== undefined ? { min: transition.min } : {}),
55
+ ...(transition.max !== undefined ? { max: transition.max } : {}),
56
+ })
57
+ value.set(from)
58
+ resolveDefaultValueChannel().graph.animate(value, () => factory({ from, velocity }))
59
+ }
60
+
61
+ /**
62
+ * Velocity-seeded spring on an external MotionValue (REQ-GESTURE-022 parity with host
63
+ * driver.command release). The MV must belong to the default value-channel graph
64
+ * (useMotionValue / useSpring followers do).
65
+ */
66
+ function springReleaseExternal(
67
+ value: MotionValue<number>,
68
+ from: number,
69
+ to: number,
70
+ velocity: number,
71
+ transition: PropTransition,
72
+ ): void {
73
+ const config = springConfigFromRelease(transition)
74
+ const factory = resolveSpringGenerator(to, config)
75
+ // Seed the handoff origin so the spring starts from the release sample, not a stale MV.
76
+ value.set(from)
77
+ try {
78
+ resolveDefaultValueChannel().graph.animate(value, () => factory({ from, velocity }))
79
+ } catch {
80
+ // MV not owned by the default graph (hostile/test double) — land at target fail-closed.
81
+ value.set(to)
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Wrap a Driver so drag traffic for the named axes lands on external MotionValues.
87
+ * Other axes and non-handle traffic pass through unchanged.
88
+ */
89
+ export function wrapDriverForExternalDragValues(
90
+ driver: Driver,
91
+ handle: ElementHandle,
92
+ external: ExternalDragValues,
93
+ ): Driver {
94
+ const hasX = external.x !== undefined
95
+ const hasY = external.y !== undefined
96
+ if (!hasX && !hasY) return driver
97
+
98
+ return {
99
+ register: (initial) => driver.register(initial),
100
+ step: (elapsedMs) => driver.step(elapsedMs),
101
+ committed: (h) => driver.committed(h),
102
+ setActive: (h, active) => driver.setActive(h, active),
103
+ isActive: (h) => driver.isActive(h),
104
+ liveFor(h, key) {
105
+ if (h === handle && (key === 'x' || key === 'y')) {
106
+ const mv = external[key]
107
+ if (mv !== undefined) {
108
+ const value = mv.get()
109
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
110
+ throw new Error(
111
+ `external _drag${key.toUpperCase()} must hold a finite number, got ${String(value)} (REQ-API-046).`,
112
+ )
113
+ }
114
+ const velocity = mv.getVelocity()
115
+ return { value, velocity: Number.isFinite(velocity) ? velocity : 0 }
116
+ }
117
+ }
118
+ return driver.liveFor(h, key)
119
+ },
120
+ write(h, values, velocities) {
121
+ if (h !== handle) {
122
+ driver.write(h, values, velocities)
123
+ return
124
+ }
125
+ const rest: Record<string, number> = {}
126
+ const restVel: Record<string, number> = {}
127
+ for (const [key, value] of Object.entries(values)) {
128
+ if ((key === 'x' || key === 'y') && external[key] !== undefined) {
129
+ external[key]!.set(value)
130
+ } else {
131
+ rest[key] = value
132
+ if (velocities !== undefined && velocities[key] !== undefined) {
133
+ restVel[key] = velocities[key]!
134
+ }
135
+ }
136
+ }
137
+ if (Object.keys(rest).length > 0) {
138
+ driver.write(h, rest, Object.keys(restVel).length > 0 ? restVel : undefined)
139
+ }
140
+ },
141
+ command(h, command) {
142
+ if (h !== handle || command.kind !== 'start') {
143
+ driver.command(h, command)
144
+ return
145
+ }
146
+ const restTargets: Record<string, (typeof command.targets)[string]> = {}
147
+ const releaseTransition: PropTransition = command.transition
148
+ for (const [key, target] of Object.entries(command.targets)) {
149
+ if ((key === 'x' || key === 'y') && external[key] !== undefined && target !== undefined) {
150
+ const to = target.to
151
+ if (typeof to !== 'number' || !Number.isFinite(to)) {
152
+ throw new Error(
153
+ `external _drag${key.toUpperCase()} release target must be a finite number (REQ-API-046).`,
154
+ )
155
+ }
156
+ const from = typeof target.from === 'number' ? target.from : external[key]!.get()
157
+ // R16 review minor 8: `from` is the generator seed origin — a non-finite one (a hostile
158
+ // MV's get(), or a corrupt descriptor field) would silently NaN the whole trajectory.
159
+ // Same fail-loud law as the `to` check above and the liveFor value guard.
160
+ if (!Number.isFinite(from)) {
161
+ throw new Error(
162
+ `external _drag${key.toUpperCase()} release origin must be a finite number, got ${String(from)} (REQ-API-046).`,
163
+ )
164
+ }
165
+ // Fix-up review: velocity is the last descriptor field without a finiteness guard —
166
+ // `typeof NaN === 'number'`, so a bare typeof check launders a non-finite velocity into
167
+ // a silent NaN trajectory (inertia) or a NaN-seeded spring. Same law as `from`/`to`.
168
+ const velocity = typeof target.velocity === 'number' ? target.velocity : 0
169
+ if (!Number.isFinite(velocity)) {
170
+ throw new Error(
171
+ `external _drag${key.toUpperCase()} release velocity must be a finite number, got ${String(velocity)} (REQ-API-046).`,
172
+ )
173
+ }
174
+ const targetTransition: PropTransition = target.transition ?? command.transition
175
+ if (targetTransition.type === 'inertia') {
176
+ // R16 review minor 7: this channel consumes the raw per-target descriptor WITHOUT
177
+ // core's prepare validation. A non-positive/non-finite boundary-spring pair
178
+ // degenerates the solver to a silent NaN trajectory — refuse it here (the config
179
+ // boundary refuses it at mount on the public path; this guards the direct seam).
180
+ if (
181
+ !Number.isFinite(targetTransition.bounceStiffness) ||
182
+ targetTransition.bounceStiffness <= 0 ||
183
+ !Number.isFinite(targetTransition.bounceDamping) ||
184
+ targetTransition.bounceDamping <= 0
185
+ ) {
186
+ throw new Error(
187
+ `external _drag${key.toUpperCase()} inertia release requires finite, greater-than-zero ` +
188
+ `bounceStiffness/bounceDamping, got bounceStiffness=${String(targetTransition.bounceStiffness)} ` +
189
+ `bounceDamping=${String(targetTransition.bounceDamping)} (REQ-API-046).`,
190
+ )
191
+ }
192
+ // Free-drag release (R16): the per-target inertia descriptor drives the two-phase
193
+ // generator on the external MV — decay + boundary spring, never the obsolete
194
+ // clamped spring and never a synchronous jump on an out-of-bounds origin.
195
+ inertiaReleaseExternal(external[key]!, from, velocity, targetTransition)
196
+ continue
197
+ }
198
+ // Cancel / momentum=false (v=0) on the spring path: land without a spring flight.
199
+ // Momentum=true: velocity-seeded spring to project-then-snap target (host parity).
200
+ if (velocity === 0) {
201
+ external[key]!.set(to)
202
+ } else {
203
+ springReleaseExternal(external[key]!, from, to, velocity, releaseTransition)
204
+ }
205
+ } else if (target !== undefined) {
206
+ restTargets[key] = target
207
+ }
208
+ }
209
+ if (Object.keys(restTargets).length > 0) {
210
+ driver.command(h, { ...command, targets: restTargets })
211
+ }
212
+ },
213
+ }
214
+ }
@@ -0,0 +1,31 @@
1
+ // REQ-API-058 — public `frameData`, the pin's read-only JS-frame clock.
2
+ // Pin: motion-dom frameloop/batcher.ts state `{delta, timestamp, isProcessing}`,
3
+ // first elapsed 1000/60, subsequent delta clamped [1, 40] ms, frozen at idle.
4
+ // Native timebase is the JS-thread clock (packet F1). The `frame` scheduler is not shipped.
5
+
6
+ export interface FrameData {
7
+ delta: number
8
+ timestamp: number
9
+ isProcessing: boolean
10
+ }
11
+
12
+ export function createFrameDataState(): FrameData {
13
+ return { delta: 0, timestamp: 0, isProcessing: false }
14
+ }
15
+
16
+ /** The public singleton. Mutated in place like the pin's batcher state; never replaced. */
17
+ export const frameData: FrameData = createFrameDataState()
18
+
19
+ const MAX_ELAPSED_MS = 40
20
+ const DEFAULT_ELAPSED_MS = 1000 / 60
21
+
22
+ export function applyFrameDataTick(
23
+ state: FrameData,
24
+ timestamp: number,
25
+ isFirstAfterIdle: boolean,
26
+ ): void {
27
+ state.delta = isFirstAfterIdle
28
+ ? DEFAULT_ELAPSED_MS
29
+ : Math.max(Math.min(timestamp - state.timestamp, MAX_ELAPSED_MS), 1)
30
+ state.timestamp = timestamp
31
+ }
@@ -0,0 +1,107 @@
1
+ // The gesture binding's brain (specs/M2.4-BUILD-PACKET.md): platform Pan events → validated
2
+ // GestureSamples → session calls. The session owns EVERY semantic (dead-zone, continuity,
3
+ // constraints, snap, cancel targets, release springs); this layer owns only (a) fail-loud event
4
+ // shape validation (never coerce a missing field to 0 — the M2.2/M2.3 boundary lesson) and (b) the
5
+ // clock discipline: samples are timestamped off the SAME injectable clock the stepper advances.
6
+ //
7
+ import {
8
+ createGestureSession,
9
+ type Clock,
10
+ type DragConstraints,
11
+ type GestureSample,
12
+ type GestureSession,
13
+ type MotionGraph,
14
+ type SnapConfig,
15
+ type SpringConfig,
16
+ } from '@unrulysystems/native-motion-core'
17
+
18
+ // The fields this binding reads off a gesture-handler Pan event. Kept loose (unknown) because the
19
+ // validation IS the contract — a malformed event fails loud at this boundary.
20
+ export interface PanEventShape {
21
+ readonly translationX?: unknown
22
+ readonly velocityX?: unknown
23
+ }
24
+
25
+ export interface GestureBindingOptions {
26
+ readonly graph: MotionGraph
27
+ // The same clock the stepper advances — never Date.now(), never a second time source.
28
+ readonly clock: Clock
29
+ readonly initial?: number
30
+ readonly constraints?: DragConstraints
31
+ readonly snap?: SnapConfig
32
+ readonly releaseSpring?: SpringConfig
33
+ }
34
+
35
+ export interface GestureBinding {
36
+ onStart(event: PanEventShape): void
37
+ onUpdate(event: PanEventShape): void
38
+ onEnd(event: PanEventShape): void
39
+ onCancel(): void
40
+ onFail(): void
41
+ readonly session: GestureSession
42
+ }
43
+
44
+ // Fail-loud field extraction (packet §semantics 7): a missing or non-numeric recognizer field is a
45
+ // caller/platform bug — naming the field beats silently coercing to 0 and dragging to the origin.
46
+ function requireNumber(value: unknown, field: string): number {
47
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
48
+ throw new Error(
49
+ `gesture binding: Pan event field '${field}' is ${JSON.stringify(value)} — a finite number ` +
50
+ 'is required and is never coerced (specs/M2.4-BUILD-PACKET.md §semantics 7).',
51
+ )
52
+ }
53
+ return value
54
+ }
55
+
56
+ export function createGestureBinding(options: GestureBindingOptions): GestureBinding {
57
+ const session = createGestureSession({
58
+ graph: options.graph,
59
+ ...(options.initial === undefined ? {} : { initial: options.initial }),
60
+ ...(options.constraints === undefined ? {} : { constraints: options.constraints }),
61
+ ...(options.snap === undefined ? {} : { snap: options.snap }),
62
+ ...(options.releaseSpring === undefined ? {} : { releaseSpring: options.releaseSpring }),
63
+ })
64
+ const clock = options.clock
65
+
66
+ // Validate BEFORE any session call so a malformed event never half-applies, and timestamp off the
67
+ // injected clock — the platform velocity passes through untouched (§G).
68
+ function toSample(event: PanEventShape): GestureSample {
69
+ return {
70
+ t: clock.now(),
71
+ translation: requireNumber(event.translationX, 'translationX'),
72
+ velocity: requireNumber(event.velocityX, 'velocityX'),
73
+ }
74
+ }
75
+
76
+ return {
77
+ onStart(event) {
78
+ session.begin(requireNumber(event.translationX, 'translationX'))
79
+ },
80
+ onUpdate(event) {
81
+ session.active(toSample(event))
82
+ },
83
+ onEnd(event) {
84
+ session.end(toSample(event))
85
+ },
86
+ onCancel() {
87
+ session.cancel()
88
+ },
89
+ onFail() {
90
+ session.fail()
91
+ },
92
+ session,
93
+ }
94
+ }
95
+
96
+ // RNGH end/finalize routing (review cycle 1 major 1): gesture-handler fires `onEnd(event, false)`
97
+ // for gestures that FAIL or are CANCELLED — before `onFinalize(event, false)`. Only a SUCCESSFUL
98
+ // end is a release; an unsuccessful end must leave the session active so the finalize path can
99
+ // cancel to the pre-grab value (REQ-GESTURE-018 totality). These helpers ARE the screen's wiring,
100
+ // exported so the checks drive the exact routing.
101
+ export function routePanEnd(binding: GestureBinding, event: PanEventShape, success: boolean): void {
102
+ if (success) binding.onEnd(event)
103
+ }
104
+
105
+ export function routePanFinalize(binding: GestureBinding, success: boolean): void {
106
+ if (!success && binding.session.state() === 'active') binding.onCancel()
107
+ }