@rafaelaugusto/jolt 0.1.0-beta.12 → 0.1.0-beta.13

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 (103) hide show
  1. package/README.md +4 -1
  2. package/dist/animate.d.ts +6 -4
  3. package/dist/animate.d.ts.map +1 -1
  4. package/dist/animate.js +3 -1
  5. package/dist/animate.js.map +1 -1
  6. package/dist/drag.js +3 -3
  7. package/dist/drag.js.map +1 -1
  8. package/dist/driver/android.d.ts.map +1 -1
  9. package/dist/driver/android.js +2 -0
  10. package/dist/driver/android.js.map +1 -1
  11. package/dist/driver/frame.d.ts +3 -0
  12. package/dist/driver/frame.d.ts.map +1 -0
  13. package/dist/driver/frame.js +7 -0
  14. package/dist/driver/frame.js.map +1 -0
  15. package/dist/driver/index.d.ts +2 -0
  16. package/dist/driver/index.d.ts.map +1 -1
  17. package/dist/driver/index.js.map +1 -1
  18. package/dist/driver/ios.d.ts.map +1 -1
  19. package/dist/driver/ios.js +2 -0
  20. package/dist/driver/ios.js.map +1 -1
  21. package/dist/engine.d.ts +2 -1
  22. package/dist/engine.d.ts.map +1 -1
  23. package/dist/engine.js +23 -2
  24. package/dist/engine.js.map +1 -1
  25. package/dist/image-effect.d.ts +1 -0
  26. package/dist/image-effect.d.ts.map +1 -1
  27. package/dist/image-effect.js +10 -2
  28. package/dist/image-effect.js.map +1 -1
  29. package/dist/index.d.ts +5 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +2 -0
  32. package/dist/index.js.map +1 -1
  33. package/dist/motion-scope.d.ts +2 -1
  34. package/dist/motion-scope.d.ts.map +1 -1
  35. package/dist/motion-scope.js +4 -1
  36. package/dist/motion-scope.js.map +1 -1
  37. package/dist/motion-value.d.ts +45 -8
  38. package/dist/motion-value.d.ts.map +1 -1
  39. package/dist/motion-value.js +289 -91
  40. package/dist/motion-value.js.map +1 -1
  41. package/dist/native/index.d.ts +6 -0
  42. package/dist/native/index.d.ts.map +1 -0
  43. package/dist/native/index.js +3 -0
  44. package/dist/native/index.js.map +1 -0
  45. package/dist/native-properties.d.ts +28 -0
  46. package/dist/native-properties.d.ts.map +1 -0
  47. package/dist/native-properties.js +154 -0
  48. package/dist/native-properties.js.map +1 -0
  49. package/dist/native-property-driver.d.ts +19 -0
  50. package/dist/native-property-driver.d.ts.map +1 -0
  51. package/dist/native-property-driver.js +69 -0
  52. package/dist/native-property-driver.js.map +1 -0
  53. package/dist/native-property.d.ts +35 -0
  54. package/dist/native-property.d.ts.map +1 -0
  55. package/dist/native-property.js +277 -0
  56. package/dist/native-property.js.map +1 -0
  57. package/dist/playback-group.d.ts.map +1 -1
  58. package/dist/playback-group.js +17 -1
  59. package/dist/playback-group.js.map +1 -1
  60. package/dist/playback.d.ts +15 -0
  61. package/dist/playback.d.ts.map +1 -1
  62. package/dist/playback.js +23 -0
  63. package/dist/playback.js.map +1 -1
  64. package/dist/sequence.d.ts +24 -0
  65. package/dist/sequence.d.ts.map +1 -1
  66. package/dist/sequence.js +181 -19
  67. package/dist/sequence.js.map +1 -1
  68. package/dist/timed-animation.d.ts +38 -0
  69. package/dist/timed-animation.d.ts.map +1 -0
  70. package/dist/timed-animation.js +177 -0
  71. package/dist/timed-animation.js.map +1 -0
  72. package/dist/value-observers.d.ts +19 -0
  73. package/dist/value-observers.d.ts.map +1 -0
  74. package/dist/value-observers.js +111 -0
  75. package/dist/value-observers.js.map +1 -0
  76. package/dist/value-track.d.ts +13 -0
  77. package/dist/value-track.d.ts.map +1 -0
  78. package/dist/value-track.js +59 -0
  79. package/dist/value-track.js.map +1 -0
  80. package/package.json +6 -2
  81. package/src/android-plugin.d.ts +37 -0
  82. package/src/animate.ts +13 -7
  83. package/src/drag.ts +3 -3
  84. package/src/driver/android.ts +2 -0
  85. package/src/driver/frame.ts +10 -0
  86. package/src/driver/index.ts +2 -0
  87. package/src/driver/ios.ts +2 -0
  88. package/src/engine.ts +23 -2
  89. package/src/image-effect.ts +10 -2
  90. package/src/index.ts +5 -0
  91. package/src/ios-plugin.d.ts +33 -0
  92. package/src/motion-scope.ts +5 -1
  93. package/src/motion-value.ts +331 -99
  94. package/src/native/index.ts +16 -0
  95. package/src/native-properties.ts +188 -0
  96. package/src/native-property-driver.ts +123 -0
  97. package/src/native-property.ts +353 -0
  98. package/src/playback-group.ts +17 -1
  99. package/src/playback.ts +41 -0
  100. package/src/sequence.ts +209 -20
  101. package/src/timed-animation.ts +192 -0
  102. package/src/value-observers.ts +133 -0
  103. package/src/value-track.ts +71 -0
@@ -0,0 +1,133 @@
1
+ import { Application, Color, Trace } from '@nativescript/core'
2
+ import { getDriver } from './driver/index.js'
3
+
4
+ export interface SubscribeOptions {
5
+ /** Deliver the current value synchronously when subscribing. Defaults to true. */
6
+ immediate?: boolean
7
+ }
8
+
9
+ /** `time` is milliseconds on the native animation clock, shared by a notification batch. */
10
+ export type ValueListener<T> = (value: T, time: number) => void
11
+
12
+ interface Observer {
13
+ read(value: number): number
14
+ color: boolean
15
+ listener: ValueListener<number | Color>
16
+ previous: number
17
+ }
18
+
19
+ // Only sources that need a frame live here. Idle subscriptions retain no global root reference.
20
+ const pending = new Set<ValueObservers>()
21
+ let cancelFrame: (() => void) | undefined
22
+ let listening = false
23
+ let suspended = false
24
+
25
+ function suspend() {
26
+ suspended = true
27
+ cancelFrame?.()
28
+ cancelFrame = undefined
29
+ }
30
+
31
+ function resume() {
32
+ suspended = false
33
+ schedule()
34
+ }
35
+
36
+ function schedule() {
37
+ if (!pending.size) {
38
+ cancelFrame?.()
39
+ cancelFrame = undefined
40
+ if (listening && !suspended) {
41
+ Application.off(Application.suspendEvent, suspend)
42
+ Application.off(Application.resumeEvent, resume)
43
+ listening = false
44
+ }
45
+ return
46
+ }
47
+ if (!listening) {
48
+ Application.on(Application.suspendEvent, suspend)
49
+ Application.on(Application.resumeEvent, resume)
50
+ listening = true
51
+ }
52
+ if (cancelFrame || suspended) return
53
+ cancelFrame = getDriver().frame(() => {
54
+ cancelFrame = undefined
55
+ const time = getDriver().now()
56
+ const sources = [...pending]
57
+ pending.clear()
58
+ // Capture every source before calling any listener: reentrant writes belong to the next batch.
59
+ const deliveries = sources.map((source) => source.snapshot(time))
60
+ for (const source of sources) if (source.active) pending.add(source)
61
+ for (const deliver of deliveries) deliver()
62
+ schedule()
63
+ })
64
+ }
65
+
66
+ function deliver(observer: Observer, value: number, time: number) {
67
+ try {
68
+ observer.listener(observer.color ? new Color(value) : value, time)
69
+ } catch (error) {
70
+ // A consumer failure must not interrupt other consumers or strand the shared frame source.
71
+ Trace.write(
72
+ `Jolt motion value subscriber failed: ${error instanceof Error ? error.stack : String(error)}`,
73
+ Trace.categories.Error,
74
+ Trace.messageType.error,
75
+ )
76
+ }
77
+ }
78
+
79
+ /** Root-owned subscriptions; mappings are evaluated only for consumers that opted in. */
80
+ export class ValueObservers {
81
+ private readonly observers = new Set<Observer>()
82
+ private running = false
83
+
84
+ constructor(private readonly read: (time: number) => number) {}
85
+
86
+ get active() {
87
+ return this.running && this.observers.size > 0
88
+ }
89
+
90
+ subscribe(
91
+ read: (value: number) => number,
92
+ color: boolean,
93
+ listener: ValueListener<number | Color>,
94
+ options: SubscribeOptions = {},
95
+ ): () => void {
96
+ if (typeof listener !== 'function')
97
+ throw new TypeError('subscribe() needs a listener function.')
98
+ const time = getDriver().now()
99
+ const observer = { read, color, listener, previous: read(this.read(time)) }
100
+ this.observers.add(observer)
101
+ if (options.immediate !== false) deliver(observer, observer.previous, time)
102
+ if (this.active) this.changed(true)
103
+ return () => {
104
+ this.observers.delete(observer)
105
+ if (!this.observers.size) {
106
+ pending.delete(this)
107
+ schedule()
108
+ }
109
+ }
110
+ }
111
+
112
+ changed(running: boolean) {
113
+ this.running = running
114
+ if (this.observers.size) {
115
+ pending.add(this)
116
+ schedule()
117
+ }
118
+ }
119
+
120
+ snapshot(time: number): () => void {
121
+ const value = this.read(time)
122
+ const deliveries = [...this.observers].flatMap((observer) => {
123
+ const next = observer.read(value)
124
+ if (Object.is(next, observer.previous)) return []
125
+ observer.previous = next
126
+ return [{ observer, next }]
127
+ })
128
+ return () => {
129
+ for (const { observer, next } of deliveries)
130
+ if (this.observers.has(observer)) deliver(observer, next, time)
131
+ }
132
+ }
133
+ }
@@ -0,0 +1,71 @@
1
+ import { instantTrack, type Track } from './track.js'
2
+
3
+ export interface ValuePart {
4
+ readonly track: Track
5
+ readonly elapsed: number
6
+ readonly base: number
7
+ readonly span: number
8
+ }
9
+
10
+ /** Compile scalar addition before any binding's mapping. The snapshot is independent of later edits. */
11
+ export function valueTrack(parts: readonly ValuePart[], rest: number) {
12
+ const value = (time: number) =>
13
+ parts.reduce(
14
+ (sum, part) =>
15
+ sum + part.base + part.span * part.track.valueAt(part.elapsed + time),
16
+ rest,
17
+ )
18
+ let finite = 0
19
+ const periods: number[] = []
20
+ for (const { track, elapsed } of parts) {
21
+ if (Number.isFinite(track.end)) finite = Math.max(finite, track.end - elapsed)
22
+ else if (elapsed < track.delay) finite = Math.max(finite, track.delay - elapsed)
23
+ else periods.push(Math.max(1, track.duration * (track.reverse ? 2 : 1)))
24
+ }
25
+ if (finite <= 0 && !periods.length)
26
+ return { track: instantTrack('x', value(0)), windowed: false }
27
+ const loop = finite <= 0 && periods.length ? commonPeriod(periods) : undefined
28
+ // Bound finite compositions too: a minutes-long base must not undersample a short pulse.
29
+ const duration = finite > 0 ? Math.min(finite, 4000) : (loop ?? 4000)
30
+ const steps = Math.max(1, Math.min(6000, Math.ceil((duration * 60) / 1000)))
31
+ const times = Array.from({ length: steps + 1 }, (_, i) => i / steps)
32
+ const values = times.map((time) => value(time * duration))
33
+ const endless = loop !== undefined
34
+ const at = (time: number) =>
35
+ value(
36
+ endless
37
+ ? ((time % duration) + duration) % duration
38
+ : Math.min(duration, Math.max(0, time)),
39
+ )
40
+ const track: Track = {
41
+ property: 'x',
42
+ delay: 0,
43
+ duration,
44
+ repeat: endless ? Infinity : 0,
45
+ reverse: false,
46
+ times,
47
+ values,
48
+ to: endless ? values[0]! : values[steps]!,
49
+ end: endless ? Infinity : duration,
50
+ arrival: endless ? Infinity : duration,
51
+ valueAt: at,
52
+ velocityAt: (time) => (at(time + 0.5) - at(time - 0.5)) * 1000,
53
+ }
54
+ return {
55
+ track,
56
+ windowed:
57
+ finite > duration ||
58
+ (!endless && parts.some(({ track }) => !Number.isFinite(track.end))),
59
+ }
60
+ }
61
+
62
+ function commonPeriod(periods: readonly number[]) {
63
+ const gcd = (a: number, b: number): number => (b ? gcd(b, a % b) : a)
64
+ let total = 1
65
+ for (const period of periods) {
66
+ if (!Number.isInteger(period)) return undefined
67
+ total = (total / gcd(total, period)) * period
68
+ if (total > 30000) return undefined
69
+ }
70
+ return total
71
+ }