@rafaelaugusto/jolt 0.1.0-beta.5 → 0.1.0-beta.6

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 (150) hide show
  1. package/README.md +110 -3
  2. package/dist/animate.d.ts +4 -2
  3. package/dist/animate.d.ts.map +1 -1
  4. package/dist/animate.js +48 -11
  5. package/dist/animate.js.map +1 -1
  6. package/dist/config.d.ts +22 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +34 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/driver/android.d.ts.map +1 -1
  11. package/dist/driver/android.js +7 -0
  12. package/dist/driver/android.js.map +1 -1
  13. package/dist/driver/index.d.ts +2 -0
  14. package/dist/driver/index.d.ts.map +1 -1
  15. package/dist/driver/index.js.map +1 -1
  16. package/dist/driver/ios-color.js +1 -1
  17. package/dist/driver/ios-color.js.map +1 -1
  18. package/dist/driver/ios-decoration.js +1 -1
  19. package/dist/driver/ios-decoration.js.map +1 -1
  20. package/dist/driver/ios-playback.d.ts +1 -1
  21. package/dist/driver/ios-playback.d.ts.map +1 -1
  22. package/dist/driver/ios-playback.js +14 -0
  23. package/dist/driver/ios-playback.js.map +1 -1
  24. package/dist/driver/ios.d.ts.map +1 -1
  25. package/dist/driver/ios.js +1 -0
  26. package/dist/driver/ios.js.map +1 -1
  27. package/dist/engine.d.ts +16 -1
  28. package/dist/engine.d.ts.map +1 -1
  29. package/dist/engine.js +33 -1
  30. package/dist/engine.js.map +1 -1
  31. package/dist/index.d.ts +11 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +5 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/items.d.ts +20 -0
  36. package/dist/items.d.ts.map +1 -0
  37. package/dist/items.js +109 -0
  38. package/dist/items.js.map +1 -0
  39. package/dist/keyboard.d.ts +17 -0
  40. package/dist/keyboard.d.ts.map +1 -0
  41. package/dist/keyboard.js +116 -0
  42. package/dist/keyboard.js.map +1 -0
  43. package/dist/label.d.ts +7 -0
  44. package/dist/label.d.ts.map +1 -0
  45. package/dist/label.js +60 -0
  46. package/dist/label.js.map +1 -0
  47. package/dist/liquid.d.ts.map +1 -1
  48. package/dist/liquid.js +2 -1
  49. package/dist/liquid.js.map +1 -1
  50. package/dist/loader.d.ts.map +1 -1
  51. package/dist/loader.js +2 -1
  52. package/dist/loader.js.map +1 -1
  53. package/dist/particles.d.ts.map +1 -1
  54. package/dist/particles.js +4 -3
  55. package/dist/particles.js.map +1 -1
  56. package/dist/path.d.ts +12 -0
  57. package/dist/path.d.ts.map +1 -0
  58. package/dist/path.js +90 -0
  59. package/dist/path.js.map +1 -0
  60. package/dist/playback-group.d.ts.map +1 -1
  61. package/dist/playback-group.js +29 -1
  62. package/dist/playback-group.js.map +1 -1
  63. package/dist/press.d.ts.map +1 -1
  64. package/dist/press.js +3 -3
  65. package/dist/press.js.map +1 -1
  66. package/dist/reveal.d.ts.map +1 -1
  67. package/dist/reveal.js +3 -2
  68. package/dist/reveal.js.map +1 -1
  69. package/dist/roll-diff.d.ts +22 -0
  70. package/dist/roll-diff.d.ts.map +1 -0
  71. package/dist/roll-diff.js +36 -0
  72. package/dist/roll-diff.js.map +1 -0
  73. package/dist/roll.d.ts +29 -0
  74. package/dist/roll.d.ts.map +1 -0
  75. package/dist/roll.js +442 -0
  76. package/dist/roll.js.map +1 -0
  77. package/dist/scroll-linked.d.ts +10 -0
  78. package/dist/scroll-linked.d.ts.map +1 -0
  79. package/dist/scroll-linked.js +58 -0
  80. package/dist/scroll-linked.js.map +1 -0
  81. package/dist/sequence.d.ts +10 -5
  82. package/dist/sequence.d.ts.map +1 -1
  83. package/dist/sequence.js +202 -14
  84. package/dist/sequence.js.map +1 -1
  85. package/dist/shimmer.d.ts.map +1 -1
  86. package/dist/shimmer.js +2 -1
  87. package/dist/shimmer.js.map +1 -1
  88. package/dist/svg-path.d.ts +14 -0
  89. package/dist/svg-path.d.ts.map +1 -0
  90. package/dist/svg-path.js +281 -0
  91. package/dist/svg-path.js.map +1 -0
  92. package/dist/symbol-effect.d.ts.map +1 -1
  93. package/dist/symbol-effect.js +4 -3
  94. package/dist/symbol-effect.js.map +1 -1
  95. package/dist/text-layout.d.ts +8 -0
  96. package/dist/text-layout.d.ts.map +1 -1
  97. package/dist/text-layout.js +8 -2
  98. package/dist/text-layout.js.map +1 -1
  99. package/dist/text.d.ts +1 -1
  100. package/dist/text.d.ts.map +1 -1
  101. package/dist/text.js +60 -64
  102. package/dist/text.js.map +1 -1
  103. package/dist/track.d.ts +2 -0
  104. package/dist/track.d.ts.map +1 -1
  105. package/dist/track.js +62 -15
  106. package/dist/track.js.map +1 -1
  107. package/dist/types.d.ts +7 -1
  108. package/dist/types.d.ts.map +1 -1
  109. package/dist/vue/index.d.ts +89 -0
  110. package/dist/vue/index.d.ts.map +1 -0
  111. package/dist/vue/index.js +134 -0
  112. package/dist/vue/index.js.map +1 -0
  113. package/package.json +15 -3
  114. package/platforms/android/java/org/rafaelaugusto/jolt/KeyboardFollower.java +91 -0
  115. package/platforms/android/java/org/rafaelaugusto/jolt/NumberRoll.java +190 -0
  116. package/platforms/android/java/org/rafaelaugusto/jolt/TextReveal.java +180 -0
  117. package/platforms/android/proguard-rules.pro +6 -0
  118. package/src/android-plugin.d.ts +64 -0
  119. package/src/animate.ts +80 -30
  120. package/src/config.ts +46 -0
  121. package/src/driver/android.ts +14 -0
  122. package/src/driver/index.ts +2 -0
  123. package/src/driver/ios-color.ts +1 -1
  124. package/src/driver/ios-decoration.ts +1 -1
  125. package/src/driver/ios-playback.ts +15 -1
  126. package/src/driver/ios.ts +2 -0
  127. package/src/engine.ts +47 -2
  128. package/src/index.ts +11 -1
  129. package/src/items.ts +140 -0
  130. package/src/keyboard.ts +132 -0
  131. package/src/label.ts +76 -0
  132. package/src/liquid.ts +2 -1
  133. package/src/loader.ts +2 -1
  134. package/src/particles.ts +4 -4
  135. package/src/path.ts +124 -0
  136. package/src/playback-group.ts +34 -1
  137. package/src/press.ts +3 -5
  138. package/src/reveal.ts +3 -3
  139. package/src/roll-diff.ts +57 -0
  140. package/src/roll.ts +515 -0
  141. package/src/scroll-linked.ts +73 -0
  142. package/src/sequence.ts +199 -14
  143. package/src/shimmer.ts +2 -1
  144. package/src/svg-path.ts +355 -0
  145. package/src/symbol-effect.ts +4 -3
  146. package/src/text-layout.ts +14 -2
  147. package/src/text.ts +62 -81
  148. package/src/track.ts +89 -24
  149. package/src/types.ts +8 -1
  150. package/src/vue/index.ts +189 -0
package/src/particles.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Application, Color, isIOS, type View } from '@nativescript/core'
2
2
  import { toView } from './animate.js'
3
3
  import type { Target } from './types.js'
4
+ import { reducedMotion } from './config.js'
4
5
 
5
6
  export interface ConfettiOptions {
6
7
  /** Approximate number of particles in the burst. Defaults to 80. */
@@ -78,8 +79,7 @@ function start(target: Target, settings: Settings, overlay: boolean): () => void
78
79
  const view = toView(target)
79
80
  const running = overlay ? bursts : emitters
80
81
  running.get(view)?.()
81
- if (!isIOS || settings.rate === 0 || UIAccessibilityIsReduceMotionEnabled())
82
- return () => {}
82
+ if (!isIOS || settings.rate === 0 || reducedMotion()) return () => {}
83
83
  return new Emitter(view, settings, overlay, running).stop
84
84
  }
85
85
 
@@ -191,12 +191,12 @@ class Emitter {
191
191
  }
192
192
 
193
193
  private motionChanged = () => {
194
- if (UIAccessibilityIsReduceMotionEnabled()) this.stop()
194
+ if (reducedMotion()) this.stop()
195
195
  }
196
196
 
197
197
  private update() {
198
198
  if (!this.view.isLoaded) return
199
- if (UIAccessibilityIsReduceMotionEnabled()) return this.stop()
199
+ if (reducedMotion()) return this.stop()
200
200
  const native = this.view.nativeViewProtected as UIView | undefined
201
201
  const window = native?.window
202
202
  if (this.layer && (!native || !window)) return this.stop()
package/src/path.ts ADDED
@@ -0,0 +1,124 @@
1
+ import { parsePath, type MotionPath } from './svg-path.js'
2
+ import { reducedMotion, timeScale } from './config.js'
3
+ import { createTrack, instantTrack, type Track } from './track.js'
4
+ import {
5
+ mergeTransitions,
6
+ resolveTransition,
7
+ type Transition,
8
+ type ResolvedTransition,
9
+ } from './transition.js'
10
+ import type { Keyframes, KeyframeProperty } from './types.js'
11
+
12
+ export type PathProperty = 'x' | 'y' | 'rotate'
13
+ export interface PreparedPath {
14
+ readonly properties: readonly PathProperty[]
15
+ readonly starts: ReadonlyMap<PathProperty, number>
16
+ readonly transition: ResolvedTransition
17
+ track(property: PathProperty, offset: number, delay: number): Track
18
+ }
19
+
20
+ export function preparePath(
21
+ keyframes: Keyframes,
22
+ base: Transition,
23
+ overrides: Partial<Record<KeyframeProperty | 'path', Transition>>,
24
+ ): PreparedPath | undefined {
25
+ if (keyframes.orient !== undefined && typeof keyframes.orient !== 'boolean')
26
+ throw new TypeError('Path orient must be a boolean.')
27
+ if (keyframes.path === undefined) {
28
+ if (keyframes.orient !== undefined) throw new TypeError('orient requires a path.')
29
+ return undefined
30
+ }
31
+ if (keyframes.x !== undefined || keyframes.y !== undefined)
32
+ throw new TypeError('A path owns x and y; omit explicit x/y keyframes.')
33
+ if (keyframes.orient && keyframes.rotate !== undefined)
34
+ throw new TypeError('An oriented path owns rotate; omit explicit rotate keyframes.')
35
+ if (overrides.x || overrides.y || (keyframes.orient && overrides.rotate))
36
+ throw new TypeError('Use the path transition to time its coordinates together.')
37
+ const geometry = parsePath(keyframes.path)
38
+ const orient = keyframes.orient === true
39
+ const transition = resolveTransition(
40
+ overrides.path ? mergeTransitions(base, overrides.path) : base,
41
+ 'opacity',
42
+ 2,
43
+ )
44
+ if (
45
+ !Number.isFinite(transition.curve.type === 'tween' ? transition.curve.duration : 0) ||
46
+ !Number.isFinite(transition.delay)
47
+ )
48
+ throw new RangeError('Path duration and delay must be finite.')
49
+ const properties: readonly PathProperty[] = orient ? ['x', 'y', 'rotate'] : ['x', 'y']
50
+ const first = geometry.at(0)
51
+ let motion: ReturnType<typeof sample> | undefined
52
+ return {
53
+ properties,
54
+ starts: new Map(properties.map((property) => [property, first[property]])),
55
+ transition,
56
+ track(property, offset, requested) {
57
+ motion ??= sample(geometry, transition, orient)
58
+ const { progress, times, frames } = motion
59
+ const to = geometry.at(progress.to)[property] + offset
60
+ if (reducedMotion()) return instantTrack(property, to)
61
+ const delay = requested / timeScale()
62
+ const shift = delay - progress.delay
63
+ const pose = (time: number) =>
64
+ geometry.at(progress.valueAt(time - shift))[property] + offset
65
+ return {
66
+ ...progress,
67
+ property,
68
+ delay,
69
+ end: progress.end + shift,
70
+ arrival: progress.arrival + shift,
71
+ times,
72
+ values: offset
73
+ ? frames[property].map((value) => value + offset)
74
+ : frames[property],
75
+ to,
76
+ valueAt: pose,
77
+ velocityAt(time) {
78
+ if (time <= delay || time >= progress.end + shift) return 0
79
+ const position = progress.valueAt(time - shift)
80
+ if (position < 0 || position > 1) return 0
81
+ const epsilon = Math.min(1e-5, 0.01 / Math.max(1, geometry.length))
82
+ const a = Math.max(0, position - epsilon),
83
+ b = Math.min(1, position + epsilon)
84
+ return (
85
+ ((geometry.at(b)[property] - geometry.at(a)[property]) / (b - a)) *
86
+ progress.velocityAt(time - shift)
87
+ )
88
+ },
89
+ }
90
+ },
91
+ }
92
+ }
93
+
94
+ /** Sample scalar timing once, then project all coordinates through the same path. */
95
+ function sample(geometry: MotionPath, transition: ResolvedTransition, orient: boolean) {
96
+ const progress = createTrack('opacity', [0, 1], transition, 0)
97
+ const count = Math.max(
98
+ 1,
99
+ Math.min(
100
+ 6000,
101
+ Math.max(
102
+ Math.ceil((progress.duration / 1000) * 60),
103
+ Math.ceil(geometry.length / 2),
104
+ ),
105
+ ),
106
+ )
107
+ const times = Array.from({ length: count + 1 }, (_, i) => i / count)
108
+ const frames: Record<PathProperty, number[]> = { x: [], y: [], rotate: [] }
109
+ for (const time of times) {
110
+ const position =
111
+ time === 0
112
+ ? 0
113
+ : time === 1
114
+ ? 1
115
+ : progress.valueAt(progress.delay + time * progress.duration)
116
+ if (!Number.isFinite(position))
117
+ throw new RangeError('Path easing must return finite progress.')
118
+ const pose = geometry.at(position)
119
+ frames.x.push(pose.x)
120
+ frames.y.push(pose.y)
121
+ if (orient) frames.rotate.push(pose.rotate)
122
+ }
123
+ return { progress, times, frames }
124
+ }
@@ -1,4 +1,10 @@
1
- import { arrivalOf, setArrival, type ViewAnimation } from './engine.js'
1
+ import {
2
+ arrivalOf,
3
+ arrivalTimingOf,
4
+ setArrival,
5
+ setArrivalTiming,
6
+ type ViewAnimation,
7
+ } from './engine.js'
2
8
  import { getDriver } from './driver/index.js'
3
9
  import { createClock, type PlaybackClock } from './playback.js'
4
10
  import { validateProgress, type PlaybackAnimation } from './playback.js'
@@ -104,6 +110,33 @@ export function playbackGroup(children: readonly ViewAnimation[]): PlaybackAnima
104
110
  then: (onfulfilled, onrejected) => finished.then(onfulfilled, onrejected),
105
111
  }
106
112
  setArrival(animation, Promise.all(children.map(arrivalOf)))
113
+ const listeners = new Set<() => void>()
114
+ const timings = children.map((child) => arrivalTimingOf(child)!)
115
+ const notify = () => {
116
+ for (const callback of [...listeners]) callback()
117
+ }
118
+ const unsubscribe = timings.map((timing) => timing.subscribe(notify))
119
+ setArrivalTiming(animation, {
120
+ read() {
121
+ const states = timings.map((timing) => timing.read())
122
+ return {
123
+ arrived: settled || states.every((state) => state.arrived),
124
+ paused: paused || states.some((state) => state.paused && !state.arrived),
125
+ remaining: states.some((state) => !state.arrived && state.remaining === undefined)
126
+ ? undefined
127
+ : Math.max(0, ...states.map((state) => (state.arrived ? 0 : state.remaining!))),
128
+ }
129
+ },
130
+ subscribe(callback) {
131
+ listeners.add(callback)
132
+ return () => listeners.delete(callback)
133
+ },
134
+ })
135
+ void finished.then(() => {
136
+ unsubscribe.forEach((off) => off())
137
+ notify()
138
+ listeners.clear()
139
+ })
107
140
  ensureClock()
108
141
  finishIfIdle()
109
142
  return animation
package/src/press.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Color, type EventData, type TouchGestureEventData } from '@nativescript/core'
2
2
  import { isColorProperty } from './color.js'
3
- import { animate, toView } from './animate.js'
4
- import { isProperty, readValue, type Property } from './properties.js'
3
+ import { animate, startingValues, toView } from './animate.js'
4
+ import { readValue } from './properties.js'
5
5
  import type { SpringName, Transition } from './transition.js'
6
6
  import type { AnimateOptions, Keyframes, Target } from './types.js'
7
7
 
@@ -23,9 +23,7 @@ export function press(
23
23
  options?: AnimateOptions | SpringName,
24
24
  ): () => void {
25
25
  const view = toView(target)
26
- const properties = Object.keys(pressed).flatMap((key): Property[] =>
27
- key === 'scale' ? ['scaleX', 'scaleY'] : isProperty(key) ? [key] : [],
28
- )
26
+ const properties = [...startingValues(pressed, options).keys()]
29
27
  let rest: Keyframes | undefined
30
28
 
31
29
  const onTouch = (event: EventData) => {
package/src/reveal.ts CHANGED
@@ -3,6 +3,7 @@ import { toView } from './animate.js'
3
3
  import { resolveEasing } from './easing.js'
4
4
  import type { Animation } from './engine.js'
5
5
  import type { Target } from './types.js'
6
+ import { reducedMotion } from './config.js'
6
7
 
7
8
  export interface RevealOptions {
8
9
  /** Circle origin in DIPs from the target's top-left corner. Defaults to its center. */
@@ -243,14 +244,13 @@ class CircularAnimation implements Animation {
243
244
  }
244
245
 
245
246
  private motionChanged = () => {
246
- if (UIAccessibilityIsReduceMotionEnabled()) this.end(true)
247
+ if (reducedMotion()) this.end(true)
247
248
  }
248
249
 
249
250
  private update() {
250
251
  if (!this.opening && !this.inherited && this.view.visibility !== 'visible')
251
252
  return this.end(true, true)
252
- if (this.duration === 0 || UIAccessibilityIsReduceMotionEnabled())
253
- return this.end(true)
253
+ if (this.duration === 0 || reducedMotion()) return this.end(true)
254
254
  if (this.waitingForLoad || !this.view.isLoaded) return
255
255
  const native = this.view.nativeViewProtected as MaskedView | undefined
256
256
  if (this.shape) {
@@ -0,0 +1,57 @@
1
+ export interface Grapheme {
2
+ text: string
3
+ /** UTF-16 offsets, the units NSString locations and Java char indices use. */
4
+ start: number
5
+ end: number
6
+ }
7
+
8
+ export interface RollChange {
9
+ /** The grapheme that rolls in, placed in the new text. */
10
+ next: Grapheme
11
+ /** The grapheme it replaces at the same position from the end, when there is one. */
12
+ previous: Grapheme | undefined
13
+ }
14
+
15
+ export interface RollDiff {
16
+ /** Positions that differ, from the last one backwards. */
17
+ changes: RollChange[]
18
+ /** Old graphemes to the left of the new text's first one; they roll out alone. */
19
+ removed: Grapheme[]
20
+ }
21
+
22
+ const segmenter =
23
+ typeof Intl !== 'undefined' && 'Segmenter' in Intl
24
+ ? new Intl.Segmenter(undefined, { granularity: 'grapheme' })
25
+ : undefined
26
+
27
+ export function graphemes(text: string): Grapheme[] {
28
+ if (segmenter)
29
+ return [...segmenter.segment(text)].map(({ segment, index }) => ({
30
+ text: segment,
31
+ start: index,
32
+ end: index + segment.length,
33
+ }))
34
+ const pieces: Grapheme[] = []
35
+ let offset = 0
36
+ for (const character of text) {
37
+ pieces.push({ text: character, start: offset, end: offset + character.length })
38
+ offset += character.length
39
+ }
40
+ return pieces
41
+ }
42
+
43
+ /** Aligns both texts on their last grapheme, so the units digit stays put when the count grows. */
44
+ export function diffFromEnd(previous: string, next: string): RollDiff {
45
+ const before = graphemes(previous)
46
+ const after = graphemes(next)
47
+ const changes: RollChange[] = []
48
+ const removed: Grapheme[] = []
49
+ const count = Math.max(before.length, after.length)
50
+ for (let offset = 1; offset <= count; offset++) {
51
+ const old = before[before.length - offset]
52
+ const piece = after[after.length - offset]
53
+ if (!piece) removed.push(old!)
54
+ else if (!old || old.text !== piece.text) changes.push({ next: piece, previous: old })
55
+ }
56
+ return { changes, removed }
57
+ }