@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.
Files changed (38) hide show
  1. package/README.md +18 -0
  2. package/dist/driver/android-gradient.d.ts +5 -0
  3. package/dist/driver/android-gradient.d.ts.map +1 -0
  4. package/dist/driver/android-gradient.js +89 -0
  5. package/dist/driver/android-gradient.js.map +1 -0
  6. package/dist/driver/ios-gradient.d.ts +5 -0
  7. package/dist/driver/ios-gradient.d.ts.map +1 -0
  8. package/dist/driver/ios-gradient.js +109 -0
  9. package/dist/driver/ios-gradient.js.map +1 -0
  10. package/dist/gradient.d.ts +90 -0
  11. package/dist/gradient.d.ts.map +1 -0
  12. package/dist/gradient.js +247 -0
  13. package/dist/gradient.js.map +1 -0
  14. package/dist/index.d.ts +4 -2
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +2 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/motion-scope.d.ts +1 -1
  19. package/dist/motion-scope.d.ts.map +1 -1
  20. package/dist/motion-states.d.ts +9 -2
  21. package/dist/motion-states.d.ts.map +1 -1
  22. package/dist/motion-states.js +64 -9
  23. package/dist/motion-states.js.map +1 -1
  24. package/dist/transition.d.ts +15 -0
  25. package/dist/transition.d.ts.map +1 -1
  26. package/dist/transition.js +17 -0
  27. package/dist/transition.js.map +1 -1
  28. package/package.json +1 -1
  29. package/platforms/android/java/org/rafaelaugusto/jolt/GradientLayer.java +195 -0
  30. package/platforms/android/proguard-rules.pro +2 -0
  31. package/src/android-plugin.d.ts +29 -0
  32. package/src/driver/android-gradient.ts +109 -0
  33. package/src/driver/ios-gradient.ts +114 -0
  34. package/src/gradient.ts +372 -0
  35. package/src/index.ts +16 -2
  36. package/src/motion-scope.ts +2 -2
  37. package/src/motion-states.ts +73 -19
  38. package/src/transition.ts +30 -0
@@ -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
- const views = new Map(
55
- Object.entries(targets).map(([name, target]) => [name, toView(target)]),
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
- if (new Set(views.values()).size !== views.size)
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, [Target, Keyframes][]>()
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: [Target, Keyframes][] = []
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
- rows.push([view, frames])
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: { handle: Animation; children: PlaybackAnimation[] } | undefined
95
- const controller: MotionStates<Name> = {
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.map(([view, frames]) =>
114
- prepareAnimation(view, frames, timing),
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
  | {