@rafaelaugusto/jolt 0.1.0-beta.10 → 0.1.0-beta.11
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 +18 -0
- package/dist/driver/android-gradient.d.ts +5 -0
- package/dist/driver/android-gradient.d.ts.map +1 -0
- package/dist/driver/android-gradient.js +89 -0
- package/dist/driver/android-gradient.js.map +1 -0
- package/dist/driver/ios-gradient.d.ts +5 -0
- package/dist/driver/ios-gradient.d.ts.map +1 -0
- package/dist/driver/ios-gradient.js +109 -0
- package/dist/driver/ios-gradient.js.map +1 -0
- package/dist/gradient.d.ts +90 -0
- package/dist/gradient.d.ts.map +1 -0
- package/dist/gradient.js +247 -0
- package/dist/gradient.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/motion-scope.d.ts +1 -1
- package/dist/motion-scope.d.ts.map +1 -1
- package/dist/motion-states.d.ts +9 -2
- package/dist/motion-states.d.ts.map +1 -1
- package/dist/motion-states.js +64 -9
- package/dist/motion-states.js.map +1 -1
- package/dist/transition.d.ts +15 -0
- package/dist/transition.d.ts.map +1 -1
- package/dist/transition.js +17 -0
- package/dist/transition.js.map +1 -1
- package/package.json +1 -1
- package/platforms/android/java/org/rafaelaugusto/jolt/GradientLayer.java +195 -0
- package/platforms/android/proguard-rules.pro +2 -0
- package/src/android-plugin.d.ts +29 -0
- package/src/driver/android-gradient.ts +109 -0
- package/src/driver/ios-gradient.ts +114 -0
- package/src/gradient.ts +372 -0
- package/src/index.ts +16 -2
- package/src/motion-scope.ts +2 -2
- package/src/motion-states.ts +73 -19
- package/src/transition.ts +30 -0
package/src/motion-states.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { prepareAnimation, toView } from './animate.js'
|
|
1
|
+
import { endingValues, prepareAnimation, set, toView } from './animate.js'
|
|
2
2
|
import { isColorProperty, type ColorValue } from './color.js'
|
|
3
3
|
import { isProperty } from './properties.js'
|
|
4
4
|
import type { Animation } from './engine.js'
|
|
5
5
|
import type { PlaybackAnimation } from './playback.js'
|
|
6
|
-
import type { KeyframeProperty, Keyframes, Target } from './types.js'
|
|
6
|
+
import type { KeyframeProperty, Keyframes, Target, Values } from './types.js'
|
|
7
|
+
import type { View } from '@nativescript/core'
|
|
7
8
|
import type { Spring, Tween, SpringName } from './transition.js'
|
|
8
9
|
import type { MotionScope } from './motion-scope.js'
|
|
9
10
|
|
|
@@ -28,13 +29,20 @@ export interface MotionStatesOptions {
|
|
|
28
29
|
transition?: StateTransition
|
|
29
30
|
scope?: MotionScope
|
|
30
31
|
}
|
|
31
|
-
export interface MotionStates<S extends string = string> {
|
|
32
|
+
export interface MotionStates<S extends string = string, T extends string = string> {
|
|
32
33
|
/** Latest requested state, not a claim that playback has finished. */
|
|
33
34
|
readonly current: S | undefined
|
|
34
35
|
readonly settled: S | undefined
|
|
35
36
|
readonly disposed: boolean
|
|
36
37
|
readonly paused: boolean
|
|
37
38
|
to(state: S, transition?: StateTransition): Animation
|
|
39
|
+
/**
|
|
40
|
+
* Point a target at a new view, or at none with `null`. The new view takes the current
|
|
41
|
+
* state at once, or joins the transition in progress from where it is.
|
|
42
|
+
*/
|
|
43
|
+
attach(target: T, view: Target | null): void
|
|
44
|
+
/** A stable callback for a template ref: attaches the element and detaches it on unmount. */
|
|
45
|
+
ref(target: T): (view: Target | null) => void
|
|
38
46
|
pause(): void
|
|
39
47
|
play(): void
|
|
40
48
|
stop(): void
|
|
@@ -43,28 +51,33 @@ export interface MotionStates<S extends string = string> {
|
|
|
43
51
|
|
|
44
52
|
/** Coordinated named targets. Omitted properties freeze when leaving their previous state. */
|
|
45
53
|
export function motionStates<
|
|
46
|
-
T extends Record<string, Target>,
|
|
54
|
+
T extends Record<string, Target | null>,
|
|
47
55
|
const S extends Record<string, MotionState<keyof T>>,
|
|
48
56
|
>(
|
|
49
57
|
targets: T,
|
|
50
58
|
definitions: MotionDefinitions<T, S>,
|
|
51
59
|
options: MotionStatesOptions = {},
|
|
52
|
-
): MotionStates<Extract<keyof S, string>> {
|
|
60
|
+
): MotionStates<Extract<keyof S, string>, Extract<keyof T, string>> {
|
|
53
61
|
type Name = Extract<keyof S, string>
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
type Slot = Extract<keyof T, string>
|
|
63
|
+
const views = new Map<string, View | null>(
|
|
64
|
+
Object.entries(targets).map(([name, target]) => [
|
|
65
|
+
name,
|
|
66
|
+
target === null ? null : toView(target),
|
|
67
|
+
]),
|
|
56
68
|
)
|
|
57
|
-
|
|
69
|
+
const assigned = [...views.values()].filter(Boolean)
|
|
70
|
+
if (new Set(assigned).size !== assigned.length)
|
|
58
71
|
throw new TypeError('Motion state targets must refer to distinct views.')
|
|
59
|
-
const states = new Map<string, [
|
|
72
|
+
const states = new Map<string, [string, Keyframes][]>()
|
|
60
73
|
const defaults = normalize(options.transition)
|
|
61
74
|
for (const [name, state] of Object.entries(definitions)) {
|
|
62
75
|
if (!state || typeof state !== 'object' || Array.isArray(state))
|
|
63
76
|
throw new TypeError(`Invalid motion state "${name}".`)
|
|
64
|
-
const rows: [
|
|
77
|
+
const rows: [string, Keyframes][] = []
|
|
65
78
|
for (const [target, values] of Object.entries(state)) {
|
|
79
|
+
if (!views.has(target)) throw new TypeError(`Unknown motion target "${target}".`)
|
|
66
80
|
const view = views.get(target)
|
|
67
|
-
if (!view) throw new TypeError(`Unknown motion target "${target}".`)
|
|
68
81
|
if (!values || typeof values !== 'object' || Array.isArray(values))
|
|
69
82
|
throw new TypeError(`Invalid motion values for "${target}".`)
|
|
70
83
|
const frames: Keyframes = {}
|
|
@@ -82,8 +95,9 @@ export function motionStates<
|
|
|
82
95
|
Object.assign(frames, { [property]: value })
|
|
83
96
|
}
|
|
84
97
|
// Validate every definition up front without claiming any native property.
|
|
85
|
-
prepareAnimation(view, frames, defaults)
|
|
86
|
-
|
|
98
|
+
if (view) prepareAnimation(view, frames, defaults)
|
|
99
|
+
else endingValues(frames, defaults)
|
|
100
|
+
rows.push([target, frames])
|
|
87
101
|
}
|
|
88
102
|
states.set(name, rows)
|
|
89
103
|
}
|
|
@@ -91,8 +105,15 @@ export function motionStates<
|
|
|
91
105
|
let settled: Name | undefined
|
|
92
106
|
let disposed = false
|
|
93
107
|
let paused = false
|
|
94
|
-
let active:
|
|
95
|
-
|
|
108
|
+
let active:
|
|
109
|
+
| { handle: Animation; children: PlaybackAnimation[]; timing: StateTransition }
|
|
110
|
+
| undefined
|
|
111
|
+
const refs = new Map<string, (view: Target | null) => void>()
|
|
112
|
+
const framesFor = (state: Name | undefined, target: string) =>
|
|
113
|
+
state === undefined
|
|
114
|
+
? undefined
|
|
115
|
+
: states.get(state)!.find(([slot]) => slot === target)?.[1]
|
|
116
|
+
const controller: MotionStates<Name, Slot> = {
|
|
96
117
|
get current() {
|
|
97
118
|
return current
|
|
98
119
|
},
|
|
@@ -110,9 +131,10 @@ export function motionStates<
|
|
|
110
131
|
const rows = states.get(name)
|
|
111
132
|
if (!rows) throw new TypeError(`Unknown motion state "${name}".`)
|
|
112
133
|
const timing = transition === undefined ? defaults : normalize(transition)
|
|
113
|
-
const prepared = rows.
|
|
114
|
-
|
|
115
|
-
|
|
134
|
+
const prepared = rows.flatMap(([target, frames]) => {
|
|
135
|
+
const view = views.get(target)
|
|
136
|
+
return view ? [prepareAnimation(view, frames, timing)] : []
|
|
137
|
+
})
|
|
116
138
|
const previous = active
|
|
117
139
|
const children: PlaybackAnimation[] = []
|
|
118
140
|
// Start replacements first: the engine transfers visible position AND velocity.
|
|
@@ -148,12 +170,44 @@ export function motionStates<
|
|
|
148
170
|
},
|
|
149
171
|
then: (resolve, reject) => finished.then(resolve, reject),
|
|
150
172
|
}
|
|
151
|
-
active = { handle, children }
|
|
173
|
+
active = { handle, children, timing }
|
|
152
174
|
current = name
|
|
153
175
|
// Also cancels old, omitted properties, without touching the new owners.
|
|
154
176
|
previous?.handle.stop()
|
|
155
177
|
return handle
|
|
156
178
|
},
|
|
179
|
+
attach(target, next) {
|
|
180
|
+
if (disposed) throw new Error('Motion states controller is disposed.')
|
|
181
|
+
if (!views.has(target)) throw new TypeError(`Unknown motion target "${target}".`)
|
|
182
|
+
const view = next === null ? null : toView(next)
|
|
183
|
+
if (views.get(target) === view) return
|
|
184
|
+
if (view)
|
|
185
|
+
for (const [other, assignedView] of views)
|
|
186
|
+
if (other !== target && assignedView === view)
|
|
187
|
+
throw new TypeError('Motion state targets must refer to distinct views.')
|
|
188
|
+
if (view)
|
|
189
|
+
for (const [, rows] of states)
|
|
190
|
+
for (const [slot, frames] of rows)
|
|
191
|
+
if (slot === target) prepareAnimation(view, frames, defaults)
|
|
192
|
+
views.set(target, view)
|
|
193
|
+
const frames = view ? framesFor(current, target) : undefined
|
|
194
|
+
if (!view || !frames) return
|
|
195
|
+
if (active) {
|
|
196
|
+
const child = prepareAnimation(view, frames, active.timing)()
|
|
197
|
+
active.children.push(child)
|
|
198
|
+
if (paused) child.pause()
|
|
199
|
+
} else set(view, frames as Values)
|
|
200
|
+
},
|
|
201
|
+
ref(target) {
|
|
202
|
+
let callback = refs.get(target)
|
|
203
|
+
if (!callback) {
|
|
204
|
+
callback = (view) => {
|
|
205
|
+
if (!disposed) controller.attach(target, view)
|
|
206
|
+
}
|
|
207
|
+
refs.set(target, callback)
|
|
208
|
+
}
|
|
209
|
+
return callback
|
|
210
|
+
},
|
|
157
211
|
pause() {
|
|
158
212
|
if (disposed || paused) return
|
|
159
213
|
paused = true
|
package/src/transition.ts
CHANGED
|
@@ -54,6 +54,36 @@ export const springs: Record<SpringName, Spring> = {
|
|
|
54
54
|
bouncy: { type: 'spring', duration: 550, bounce: 0.4 },
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
export interface OrigamiSpring {
|
|
58
|
+
/** React Native's default is 7. */
|
|
59
|
+
friction?: number
|
|
60
|
+
/** React Native's default is 40. */
|
|
61
|
+
tension?: number
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The spring React Native's `Animated.spring` plays for `friction` and `tension`,
|
|
66
|
+
* converted with the same Origami formulas.
|
|
67
|
+
*
|
|
68
|
+
* ```ts
|
|
69
|
+
* animate(card, { x: 100 }, origamiSpring({ friction: 7, tension: 40 }))
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export function origamiSpring({
|
|
73
|
+
friction = 7,
|
|
74
|
+
tension = 40,
|
|
75
|
+
}: OrigamiSpring = {}): Spring {
|
|
76
|
+
if (!Number.isFinite(friction) || !Number.isFinite(tension))
|
|
77
|
+
throw new RangeError('Origami friction and tension must be finite.')
|
|
78
|
+
const stiffness = (tension - 30) * 3.62 + 194
|
|
79
|
+
const damping = (friction - 8) * 3 + 25
|
|
80
|
+
if (stiffness <= 0 || damping < 0)
|
|
81
|
+
throw new RangeError(
|
|
82
|
+
`Origami tension ${tension} and friction ${friction} give no spring (stiffness ${stiffness}, damping ${damping}).`,
|
|
83
|
+
)
|
|
84
|
+
return { type: 'spring', stiffness, damping, mass: 1 }
|
|
85
|
+
}
|
|
86
|
+
|
|
57
87
|
export type Curve =
|
|
58
88
|
| DragCurve
|
|
59
89
|
| {
|