@rafaelaugusto/jolt 0.1.0-beta.15 → 0.1.0-beta.17

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 (132) hide show
  1. package/README.md +9 -2
  2. package/dist/bottom-sheet/geometry.d.ts +18 -0
  3. package/dist/bottom-sheet/geometry.d.ts.map +1 -0
  4. package/dist/bottom-sheet/geometry.js +51 -0
  5. package/dist/bottom-sheet/geometry.js.map +1 -0
  6. package/dist/bottom-sheet/index.d.ts +56 -0
  7. package/dist/bottom-sheet/index.d.ts.map +1 -0
  8. package/dist/bottom-sheet/index.js +462 -0
  9. package/dist/bottom-sheet/index.js.map +1 -0
  10. package/dist/bottom-sheet/native.d.ts +11 -0
  11. package/dist/bottom-sheet/native.d.ts.map +1 -0
  12. package/dist/bottom-sheet/native.js +173 -0
  13. package/dist/bottom-sheet/native.js.map +1 -0
  14. package/dist/drag.js +1 -1
  15. package/dist/drag.js.map +1 -1
  16. package/dist/driver/android.d.ts.map +1 -1
  17. package/dist/driver/android.js +2 -0
  18. package/dist/driver/android.js.map +1 -1
  19. package/dist/driver/ios-scroll-delegate.d.ts +4 -0
  20. package/dist/driver/ios-scroll-delegate.d.ts.map +1 -0
  21. package/dist/driver/ios-scroll-delegate.js +26 -0
  22. package/dist/driver/ios-scroll-delegate.js.map +1 -0
  23. package/dist/driver/ios.d.ts.map +1 -1
  24. package/dist/driver/ios.js +2 -0
  25. package/dist/driver/ios.js.map +1 -1
  26. package/dist/gesture-native.d.ts +28 -0
  27. package/dist/gesture-native.d.ts.map +1 -0
  28. package/dist/gesture-native.js +61 -0
  29. package/dist/gesture-native.js.map +1 -0
  30. package/dist/gestures/index.d.ts +69 -0
  31. package/dist/gestures/index.d.ts.map +1 -0
  32. package/dist/gestures/index.js +262 -0
  33. package/dist/gestures/index.js.map +1 -0
  34. package/dist/index.d.ts +2 -2
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +1 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/live-spring-driver.d.ts +52 -0
  39. package/dist/live-spring-driver.d.ts.map +1 -0
  40. package/dist/live-spring-driver.js +104 -0
  41. package/dist/live-spring-driver.js.map +1 -0
  42. package/dist/mixer.d.ts +6 -0
  43. package/dist/mixer.d.ts.map +1 -1
  44. package/dist/mixer.js +41 -1
  45. package/dist/mixer.js.map +1 -1
  46. package/dist/motion-value.d.ts +34 -2
  47. package/dist/motion-value.d.ts.map +1 -1
  48. package/dist/motion-value.js +277 -10
  49. package/dist/motion-value.js.map +1 -1
  50. package/dist/native/index.d.ts +2 -0
  51. package/dist/native/index.d.ts.map +1 -1
  52. package/dist/native/index.js +1 -0
  53. package/dist/native/index.js.map +1 -1
  54. package/dist/native-property.d.ts +2 -0
  55. package/dist/native-property.d.ts.map +1 -1
  56. package/dist/native-property.js +12 -4
  57. package/dist/native-property.js.map +1 -1
  58. package/dist/reorder/handle.d.ts +6 -0
  59. package/dist/reorder/handle.d.ts.map +1 -0
  60. package/dist/reorder/handle.js +10 -0
  61. package/dist/reorder/handle.js.map +1 -0
  62. package/dist/reorder/index.d.ts +10 -0
  63. package/dist/reorder/index.d.ts.map +1 -0
  64. package/dist/reorder/index.js +233 -0
  65. package/dist/reorder/index.js.map +1 -0
  66. package/dist/reorder/list-view.d.ts +5 -0
  67. package/dist/reorder/list-view.d.ts.map +1 -0
  68. package/dist/reorder/list-view.js +238 -0
  69. package/dist/reorder/list-view.js.map +1 -0
  70. package/dist/reorder/model.d.ts +6 -0
  71. package/dist/reorder/model.d.ts.map +1 -0
  72. package/dist/reorder/model.js +30 -0
  73. package/dist/reorder/model.js.map +1 -0
  74. package/dist/reorder/surface.d.ts +6 -0
  75. package/dist/reorder/surface.d.ts.map +1 -0
  76. package/dist/reorder/surface.js +10 -0
  77. package/dist/reorder/surface.js.map +1 -0
  78. package/dist/reorder/types.d.ts +92 -0
  79. package/dist/reorder/types.d.ts.map +1 -0
  80. package/dist/reorder/types.js +2 -0
  81. package/dist/reorder/types.js.map +1 -0
  82. package/dist/scene-shader.d.ts +18 -0
  83. package/dist/scene-shader.d.ts.map +1 -0
  84. package/dist/scene-shader.js +76 -0
  85. package/dist/scene-shader.js.map +1 -0
  86. package/dist/value-observers.d.ts +3 -1
  87. package/dist/value-observers.d.ts.map +1 -1
  88. package/dist/value-observers.js +10 -2
  89. package/dist/value-observers.js.map +1 -1
  90. package/package.json +13 -1
  91. package/platforms/android/include.gradle +9 -0
  92. package/platforms/android/java/org/rafaelaugusto/jolt/GestureSession.java +198 -0
  93. package/platforms/android/java/org/rafaelaugusto/jolt/GestureSurface.java +53 -0
  94. package/platforms/android/java/org/rafaelaugusto/jolt/LiveSpring.java +270 -0
  95. package/platforms/android/java/org/rafaelaugusto/jolt/ReorderHandle.java +25 -0
  96. package/platforms/android/java/org/rafaelaugusto/jolt/ReorderSession.java +213 -0
  97. package/platforms/android/java/org/rafaelaugusto/jolt/ReorderSurface.java +27 -0
  98. package/platforms/android/java/org/rafaelaugusto/jolt/SpringState.java +39 -0
  99. package/platforms/android/proguard-rules.pro +5 -0
  100. package/platforms/ios/src/JoltGestureSession.h +18 -0
  101. package/platforms/ios/src/JoltGestureSession.m +222 -0
  102. package/platforms/ios/src/JoltLiveSpring.h +28 -0
  103. package/platforms/ios/src/JoltLiveSpring.m +236 -0
  104. package/platforms/ios/src/JoltReorderSession.h +15 -0
  105. package/platforms/ios/src/JoltReorderSession.m +205 -0
  106. package/platforms/ios/src/JoltSpringState.h +35 -0
  107. package/platforms/ios/src/module.modulemap +3 -0
  108. package/src/android-plugin.d.ts +93 -0
  109. package/src/bottom-sheet/geometry.ts +84 -0
  110. package/src/bottom-sheet/index.ts +536 -0
  111. package/src/bottom-sheet/native.ts +214 -0
  112. package/src/drag.ts +1 -1
  113. package/src/driver/android.ts +2 -0
  114. package/src/driver/ios-scroll-delegate.ts +23 -0
  115. package/src/driver/ios.ts +2 -0
  116. package/src/gesture-native.ts +139 -0
  117. package/src/gestures/index.ts +365 -0
  118. package/src/index.ts +3 -1
  119. package/src/ios-plugin.d.ts +90 -0
  120. package/src/live-spring-driver.ts +181 -0
  121. package/src/mixer.ts +43 -1
  122. package/src/motion-value.ts +318 -11
  123. package/src/native/index.ts +6 -0
  124. package/src/native-property.ts +17 -4
  125. package/src/reorder/handle.ts +9 -0
  126. package/src/reorder/index.ts +243 -0
  127. package/src/reorder/list-view.ts +262 -0
  128. package/src/reorder/model.ts +47 -0
  129. package/src/reorder/surface.ts +10 -0
  130. package/src/reorder/types.ts +87 -0
  131. package/src/scene-shader.ts +139 -0
  132. package/src/value-observers.ts +12 -2
@@ -0,0 +1,214 @@
1
+ import {
2
+ Application,
3
+ Screen,
4
+ Utils,
5
+ isIOS,
6
+ type ScrollView,
7
+ type View,
8
+ } from '@nativescript/core'
9
+
10
+ function firstResponder(view: UIView): UIView | undefined {
11
+ if (view.isFirstResponder) return view
12
+ for (let i = 0; i < view.subviews.count; i++) {
13
+ const found = firstResponder(view.subviews.objectAtIndex(i))
14
+ if (found) return found
15
+ }
16
+ return undefined
17
+ }
18
+
19
+ export function windowHeight(view: View): number {
20
+ if (isIOS)
21
+ return (
22
+ (view.nativeViewProtected as UIView)?.window?.bounds.size.height ??
23
+ Screen.mainScreen.heightDIPs
24
+ )
25
+ const activity =
26
+ Application.android.foregroundActivity ?? Application.android.startActivity
27
+ if (android.os.Build.VERSION.SDK_INT >= 30 && activity)
28
+ return (
29
+ activity.getWindowManager().getCurrentWindowMetrics().getBounds().height() /
30
+ Utils.layout.getDisplayDensity()
31
+ )
32
+ return Screen.mainScreen.heightDIPs
33
+ }
34
+
35
+ export function captureFocus(panel: View, returnFocus?: View): () => void {
36
+ if (returnFocus)
37
+ return () => {
38
+ if (!returnFocus.isLoaded) return
39
+ returnFocus.focus()
40
+ returnFocus.accessibilityScreenChanged()
41
+ }
42
+ if (isIOS) {
43
+ const window = (panel.nativeViewProtected as UIView)?.window
44
+ const responder = window ? firstResponder(window) : undefined
45
+ const accessible = UIAccessibilityFocusedElement(null)
46
+ return () => {
47
+ if (responder?.window) responder.becomeFirstResponder()
48
+ UIAccessibilityPostNotification(
49
+ UIAccessibilityLayoutChangedNotification,
50
+ accessible ?? responder ?? null,
51
+ )
52
+ }
53
+ }
54
+ const activity =
55
+ Application.android.foregroundActivity ?? Application.android.startActivity
56
+ const previous = activity?.getCurrentFocus()
57
+ return () => {
58
+ if (previous?.isAttachedToWindow()) {
59
+ previous.requestFocus()
60
+ previous.sendAccessibilityEvent(
61
+ android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED,
62
+ )
63
+ }
64
+ }
65
+ }
66
+
67
+ export function revealFocusedField(panel: View, scroll?: ScrollView): void {
68
+ if (!scroll?.nativeViewProtected || !panel.nativeViewProtected) return
69
+ if (isIOS) {
70
+ const focused = firstResponder(panel.nativeViewProtected as UIView)
71
+ const native = scroll.nativeViewProtected as UIScrollView
72
+ if (focused?.isDescendantOfView(native))
73
+ native.scrollRectToVisibleAnimated(
74
+ focused.convertRectToView(focused.bounds, native),
75
+ false,
76
+ )
77
+ } else {
78
+ const focused = (panel.nativeViewProtected as android.view.View).findFocus()
79
+ if (focused)
80
+ focused.requestRectangleOnScreen(
81
+ new android.graphics.Rect(0, 0, focused.getWidth(), focused.getHeight()),
82
+ true,
83
+ )
84
+ }
85
+ }
86
+
87
+ export function dismissKeyboard(panel: View): void {
88
+ if (!panel.nativeViewProtected) return
89
+ if (isIOS) (panel.nativeViewProtected as UIView).endEditing(true)
90
+ else {
91
+ const focused = (panel.nativeViewProtected as android.view.View).findFocus()
92
+ if (focused) {
93
+ const manager = focused
94
+ .getContext()
95
+ .getSystemService(
96
+ android.content.Context.INPUT_METHOD_SERVICE,
97
+ ) as android.view.inputmethod.InputMethodManager
98
+ manager.hideSoftInputFromWindow(focused.getWindowToken(), 0)
99
+ focused.clearFocus()
100
+ }
101
+ }
102
+ }
103
+
104
+ /** Add only our actions; preserve an application's existing native accessibility actions. */
105
+ export function sheetActions(
106
+ handle: View,
107
+ actions: readonly { label: string; run(): void }[],
108
+ ): () => void {
109
+ if (isIOS) {
110
+ const native = handle.nativeViewProtected as UIView
111
+ const previous = native.accessibilityCustomActions
112
+ const own = actions.map(({ label, run }) =>
113
+ UIAccessibilityCustomAction.alloc().initWithNameActionHandler(label, () => {
114
+ run()
115
+ return true
116
+ }),
117
+ )
118
+ const existing: UIAccessibilityCustomAction[] = []
119
+ if (previous)
120
+ for (let i = 0; i < previous.count; i++) existing.push(previous.objectAtIndex(i))
121
+ native.accessibilityCustomActions = NSArray.arrayWithArray([...existing, ...own])
122
+ return () => {
123
+ const current = native.accessibilityCustomActions
124
+ const retained: UIAccessibilityCustomAction[] = []
125
+ if (current)
126
+ for (let i = 0; i < current.count; i++) {
127
+ const action = current.objectAtIndex(i)
128
+ if (!own.includes(action)) retained.push(action)
129
+ }
130
+ native.accessibilityCustomActions = retained.length
131
+ ? NSArray.arrayWithArray(retained)
132
+ : null
133
+ }
134
+ }
135
+ const native = handle.nativeViewProtected as android.view.View
136
+ const compat = androidx.core.view.ViewCompat
137
+ const ids: number[] = []
138
+ let ended = false,
139
+ timer: ReturnType<typeof setTimeout> | undefined
140
+ // Core batches accessibility-style updates in a microtask and installs its delegate.
141
+ // Wrap that delegate afterwards, retaining its semantics and all existing actions.
142
+ const repair = () => {
143
+ if (ended || handle.nativeViewProtected !== native) return
144
+ compat.setAccessibilityDelegate(
145
+ native,
146
+ compat.getAccessibilityDelegate(native) ??
147
+ new androidx.core.view.AccessibilityDelegateCompat(),
148
+ )
149
+ if (ids.length) return
150
+ const node = native.createAccessibilityNodeInfo(),
151
+ existing = node.getActionList()
152
+ const used = new Set<number>()
153
+ for (let index = 0; index < existing.size(); index++)
154
+ used.add(existing.get(index).getId())
155
+ node.recycle()
156
+ const resources = native.getResources(),
157
+ packageName = native.getContext().getPackageName()
158
+ const available: number[] = []
159
+ for (let index = 0; index < 32; index++) {
160
+ const id = resources.getIdentifier(
161
+ `accessibility_custom_action_${index}`,
162
+ 'id',
163
+ packageName,
164
+ )
165
+ if (id && !used.has(id)) available.push(id)
166
+ }
167
+ if (available.length < actions.length)
168
+ throw new Error(
169
+ 'Not enough free Android accessibility actions for the sheet handle.',
170
+ )
171
+ for (let index = 0; index < actions.length; index++) {
172
+ const { label, run } = actions[index]!,
173
+ id = available[index]!
174
+ const action =
175
+ new androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat(
176
+ id,
177
+ label,
178
+ )
179
+ compat.replaceAccessibilityAction(
180
+ native,
181
+ action,
182
+ label,
183
+ new androidx.core.view.accessibility.AccessibilityViewCommand({
184
+ perform: () => {
185
+ run()
186
+ return true
187
+ },
188
+ }),
189
+ )
190
+ ids.push(id)
191
+ }
192
+ }
193
+ const schedule = () => {
194
+ if (ended || timer !== undefined) return
195
+ timer = setTimeout(() => {
196
+ timer = undefined
197
+ repair()
198
+ }, 0)
199
+ }
200
+ const changes = [
201
+ 'accessibleChange',
202
+ 'accessibilityRoleChange',
203
+ 'accessibilityStateChange',
204
+ 'accessibilityMediaSessionChange',
205
+ ]
206
+ for (const event of changes) handle.style.on(event, schedule)
207
+ schedule()
208
+ return () => {
209
+ ended = true
210
+ if (timer !== undefined) clearTimeout(timer)
211
+ for (const event of changes) handle.style.off(event, schedule)
212
+ ids.forEach((id) => compat.removeAccessibilityAction(native, id))
213
+ }
214
+ }
package/src/drag.ts CHANGED
@@ -428,7 +428,7 @@ class DragController implements Drag {
428
428
  const [axis] = this.config.axes
429
429
  const value = this.config.value
430
430
  const animation = value
431
- ? value.play(createTrack(axis!, [event[axis!]], plans[0]!.transition, 0))
431
+ ? value.playTrack(createTrack(axis!, [event[axis!]], plans[0]!.transition, 0))
432
432
  : new ViewAnimation(this.view, plans)
433
433
  this.release = animation
434
434
  void animation.finished.then((finished) => {
@@ -4,6 +4,7 @@ import {
4
4
  type AndroidPlayback,
5
5
  } from './android-playback.js'
6
6
  import { isDecorationProperty } from '../decoration.js'
7
+ import { syncLiveSprings } from '../live-spring-driver.js'
7
8
  import {
8
9
  syncAndroidDecoration,
9
10
  playAndroidDecoration,
@@ -188,6 +189,7 @@ export function createAndroidDriver(): Driver {
188
189
  sync(view) {
189
190
  syncAndroidDecoration(view)
190
191
  syncAndroidColor(view)
192
+ syncLiveSprings(view)
191
193
  },
192
194
 
193
195
  elapsed: (handle) => (handle as Handle).elapsed(),
@@ -0,0 +1,23 @@
1
+ import type { ScrollView } from '@nativescript/core'
2
+
3
+ /** Preserve Core's lazy scroll listener attach/detach without adding a JS scroll observer. */
4
+ export function retainScrollDelegate(scroll: ScrollView, refresh: () => void) {
5
+ const host = scroll as ScrollView & { attachNative(): void; detachNative(): void }
6
+ const restore: (() => void)[] = []
7
+ for (const key of ['attachNative', 'detachNative'] as const) {
8
+ const original = host[key]
9
+ if (typeof original !== 'function') continue
10
+ const descriptor = Object.getOwnPropertyDescriptor(host, key)
11
+ const wrapper = function (this: typeof host) {
12
+ original.call(this)
13
+ refresh()
14
+ }
15
+ host[key] = wrapper
16
+ restore.push(() => {
17
+ if (host[key] !== wrapper) return
18
+ if (descriptor) Object.defineProperty(host, key, descriptor)
19
+ else delete (host as Partial<typeof host>)[key]
20
+ })
21
+ }
22
+ return () => restore.forEach((action) => action())
23
+ }
package/src/driver/ios.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { iosPlayback } from './ios-playback.js'
2
+ import { syncLiveSprings } from '../live-spring-driver.js'
2
3
  import { isDecorationProperty } from '../decoration.js'
3
4
  import {
4
5
  playIOSDecoration,
@@ -197,6 +198,7 @@ export function createIOSDriver(): Driver {
197
198
  view.nativeViewProtected,
198
199
  excluding ? (keyPaths[excluding as NumericProperty] ?? null) : null,
199
200
  )
201
+ syncLiveSprings(view)
200
202
  },
201
203
 
202
204
  presented(view, property) {
@@ -0,0 +1,139 @@
1
+ import { isIOS, type View, type ScrollView } from '@nativescript/core'
2
+ import { liveValueInput, type DerivedValue } from './motion-value.js'
3
+ import { nativeLiveNode } from './live-spring-driver.js'
4
+ import { retainScrollDelegate } from './driver/ios-scroll-delegate.js'
5
+
6
+ export interface GestureSettings {
7
+ axis: number
8
+ threshold: number
9
+ failOffset: number
10
+ pinch: boolean
11
+ rotate: boolean
12
+ tap: boolean
13
+ doubleTap: boolean
14
+ contactEvents: boolean
15
+ minX: number
16
+ maxX: number
17
+ minY: number
18
+ maxY: number
19
+ minScale: number
20
+ maxScale: number
21
+ }
22
+ export interface GestureDriver {
23
+ readonly active: boolean
24
+ cancel(): void
25
+ enabled(value: boolean): void
26
+ settle(values: readonly number[], animated: boolean): void
27
+ bounds(settings: GestureSettings): void
28
+ dispose(): void
29
+ }
30
+ export function createGestureDriver(
31
+ surface: View,
32
+ scroll: ScrollView | undefined,
33
+ values: readonly DerivedValue[],
34
+ options: GestureSettings,
35
+ callback: (state: number, vx: number, vy: number) => void,
36
+ ): GestureDriver {
37
+ const inputs = values.map(liveValueInput)
38
+ const nodes = inputs.map((input) => nativeLiveNode(input.node))
39
+ const changed = (state: number, vx: number, vy: number) => {
40
+ for (const input of inputs) input.changed()
41
+ callback(state, vx, vy)
42
+ }
43
+ const json = JSON.stringify(options)
44
+ if (isIOS) {
45
+ if (typeof JoltGestureSession === 'undefined')
46
+ throw new Error('Rebuild the native app to use gestures().')
47
+ if (
48
+ options.contactEvents &&
49
+ typeof JoltGestureSession.supportsContactEvents !== 'function'
50
+ )
51
+ throw new Error(
52
+ 'Rebuild the native app to use gesture contact events and bottomSheet().',
53
+ )
54
+ const [x, y, scale, rotation] = nodes as [
55
+ JoltLiveSpring,
56
+ JoltLiveSpring,
57
+ JoltLiveSpring,
58
+ JoltLiveSpring,
59
+ ]
60
+ const native = JoltGestureSession.attachScrollXYScaleRotationOptionsOnState(
61
+ surface.nativeViewProtected,
62
+ scroll?.nativeViewProtected ?? null,
63
+ x,
64
+ y,
65
+ scale,
66
+ rotation,
67
+ json,
68
+ changed,
69
+ )
70
+ const releaseDelegate = scroll
71
+ ? retainScrollDelegate(scroll, () => native.refreshScrollDelegate())
72
+ : () => {}
73
+ return {
74
+ get active() {
75
+ return native.active
76
+ },
77
+ cancel: () => native.cancel(),
78
+ enabled: (value) => native.setEnabled(value),
79
+ settle: (values, animated) => {
80
+ native.settleXYScaleRotationAnimated(
81
+ values[0]!,
82
+ values[1]!,
83
+ values[2]!,
84
+ values[3]!,
85
+ animated,
86
+ )
87
+ inputs.forEach((input) => input.changed())
88
+ },
89
+ bounds: (settings) =>
90
+ native.updateLeftRightTopBottom(
91
+ settings.minX,
92
+ settings.maxX,
93
+ settings.minY,
94
+ settings.maxY,
95
+ ),
96
+ dispose: () => {
97
+ releaseDelegate()
98
+ native.dispose()
99
+ },
100
+ }
101
+ }
102
+ if (
103
+ options.contactEvents &&
104
+ typeof org.rafaelaugusto.jolt.GestureSession.supportsContactEvents !== 'function'
105
+ )
106
+ throw new Error(
107
+ 'Rebuild the native app to use gesture contact events and bottomSheet().',
108
+ )
109
+ const [x, y, scale, rotation] = nodes as [
110
+ org.rafaelaugusto.jolt.LiveSpring,
111
+ org.rafaelaugusto.jolt.LiveSpring,
112
+ org.rafaelaugusto.jolt.LiveSpring,
113
+ org.rafaelaugusto.jolt.LiveSpring,
114
+ ]
115
+ const native = new org.rafaelaugusto.jolt.GestureSession(
116
+ surface.nativeViewProtected,
117
+ scroll?.nativeViewProtected ?? null,
118
+ x,
119
+ y,
120
+ scale,
121
+ rotation,
122
+ json,
123
+ new org.rafaelaugusto.jolt.GestureSession.Listener({ onState: changed }),
124
+ )
125
+ return {
126
+ get active() {
127
+ return native.isActive()
128
+ },
129
+ cancel: () => native.cancel(),
130
+ enabled: (value) => native.setEnabled(value),
131
+ settle: (values, animated) => {
132
+ native.settle(values[0]!, values[1]!, values[2]!, values[3]!, animated)
133
+ inputs.forEach((input) => input.changed())
134
+ },
135
+ bounds: (settings) =>
136
+ native.updateBounds(settings.minX, settings.maxX, settings.minY, settings.maxY),
137
+ dispose: () => native.dispose(),
138
+ }
139
+ }