@rafaelaugusto/jolt 0.1.0-beta.3 → 0.1.0-beta.4

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 (80) hide show
  1. package/README.md +55 -13
  2. package/dist/animate.d.ts.map +1 -1
  3. package/dist/animate.js +24 -2
  4. package/dist/animate.js.map +1 -1
  5. package/dist/color.d.ts +11 -0
  6. package/dist/color.d.ts.map +1 -0
  7. package/dist/color.js +73 -0
  8. package/dist/color.js.map +1 -0
  9. package/dist/driver/android-color.d.ts +17 -0
  10. package/dist/driver/android-color.d.ts.map +1 -0
  11. package/dist/driver/android-color.js +134 -0
  12. package/dist/driver/android-color.js.map +1 -0
  13. package/dist/driver/android.d.ts.map +1 -1
  14. package/dist/driver/android.js +6 -0
  15. package/dist/driver/android.js.map +1 -1
  16. package/dist/driver/index.d.ts +1 -1
  17. package/dist/driver/index.d.ts.map +1 -1
  18. package/dist/driver/index.js.map +1 -1
  19. package/dist/driver/ios-color.d.ts +14 -0
  20. package/dist/driver/ios-color.d.ts.map +1 -0
  21. package/dist/driver/ios-color.js +381 -0
  22. package/dist/driver/ios-color.js.map +1 -0
  23. package/dist/driver/ios.d.ts.map +1 -1
  24. package/dist/driver/ios.js +9 -1
  25. package/dist/driver/ios.js.map +1 -1
  26. package/dist/engine.d.ts.map +1 -1
  27. package/dist/engine.js +9 -5
  28. package/dist/engine.js.map +1 -1
  29. package/dist/index.d.ts +3 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +1 -0
  32. package/dist/index.js.map +1 -1
  33. package/dist/liquid-grid.d.ts +3 -0
  34. package/dist/liquid-grid.d.ts.map +1 -0
  35. package/dist/liquid-grid.js +24 -0
  36. package/dist/liquid-grid.js.map +1 -0
  37. package/dist/liquid.d.ts +18 -0
  38. package/dist/liquid.d.ts.map +1 -0
  39. package/dist/liquid.js +229 -0
  40. package/dist/liquid.js.map +1 -0
  41. package/dist/press.d.ts.map +1 -1
  42. package/dist/press.js +6 -1
  43. package/dist/press.js.map +1 -1
  44. package/dist/properties.d.ts +4 -2
  45. package/dist/properties.d.ts.map +1 -1
  46. package/dist/properties.js +12 -1
  47. package/dist/properties.js.map +1 -1
  48. package/dist/text.d.ts.map +1 -1
  49. package/dist/text.js +3 -0
  50. package/dist/text.js.map +1 -1
  51. package/dist/track.d.ts +1 -1
  52. package/dist/track.d.ts.map +1 -1
  53. package/dist/track.js +38 -2
  54. package/dist/track.js.map +1 -1
  55. package/dist/transition.d.ts.map +1 -1
  56. package/dist/transition.js +8 -2
  57. package/dist/transition.js.map +1 -1
  58. package/dist/types.d.ts +5 -0
  59. package/dist/types.d.ts.map +1 -1
  60. package/package.json +11 -2
  61. package/platforms/android/include.gradle +7 -0
  62. package/platforms/android/java/org/rafaelaugusto/jolt/ColorTarget.java +102 -0
  63. package/platforms/android/proguard-rules.pro +6 -0
  64. package/src/animate.ts +30 -4
  65. package/src/color.ts +101 -0
  66. package/src/driver/android-color.ts +168 -0
  67. package/src/driver/android.ts +8 -4
  68. package/src/driver/index.ts +6 -1
  69. package/src/driver/ios-color.ts +432 -0
  70. package/src/driver/ios.ts +13 -3
  71. package/src/engine.ts +9 -5
  72. package/src/index.ts +3 -0
  73. package/src/liquid-grid.ts +28 -0
  74. package/src/liquid.ts +282 -0
  75. package/src/press.ts +8 -2
  76. package/src/properties.ts +15 -5
  77. package/src/text.ts +2 -0
  78. package/src/track.ts +63 -2
  79. package/src/transition.ts +10 -2
  80. package/src/types.ts +5 -0
@@ -1,4 +1,5 @@
1
1
  import { resolveEasing } from './easing.js';
2
+ import { isColorProperty } from './color.js';
2
3
  import { springPhysics } from './spring.js';
3
4
  export const springs = {
4
5
  smooth: { type: 'spring', duration: 500, bounce: 0 },
@@ -30,7 +31,10 @@ export function resolveTransition(transition, property, keyframes) {
30
31
  const type = transition.type ??
31
32
  (springKeys.some((key) => transition[key] !== undefined)
32
33
  ? 'spring'
33
- : hasCurve(transition) || property === 'opacity' || keyframes > 2
34
+ : hasCurve(transition) ||
35
+ property === 'opacity' ||
36
+ isColorProperty(property) ||
37
+ keyframes > 2
34
38
  ? 'tween'
35
39
  : 'spring');
36
40
  const delay = transition.delay ?? 0;
@@ -52,7 +56,9 @@ export function resolveTransition(transition, property, keyframes) {
52
56
  }
53
57
  const tween = transition;
54
58
  const duration = tween.duration ?? DEFAULT_DURATION;
55
- if (duration < 0 || Number.isNaN(duration)) {
59
+ if (duration < 0 ||
60
+ Number.isNaN(duration) ||
61
+ (isColorProperty(property) && !Number.isFinite(duration))) {
56
62
  throw new RangeError(`Invalid duration: ${duration}.`);
57
63
  }
58
64
  const segments = keyframes - 1;
@@ -1 +1 @@
1
- {"version":3,"file":"transition.js","sourceRoot":"","sources":["../src/transition.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"transition.js","sourceRoot":"","sources":["../src/transition.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -1,9 +1,14 @@
1
1
  import type { View } from '@nativescript/core';
2
2
  import type { Haptic } from './haptics.js';
3
3
  import type { SpringName, Transition } from './transition.js';
4
+ import type { ColorKeyframes } from './color.js';
4
5
  /** A value to animate to, or keyframes. `null` stands for the value the view has when the animation starts. */
5
6
  export type KeyframeValue = number | readonly (number | null)[];
6
7
  export interface Keyframes {
8
+ backgroundColor?: ColorKeyframes;
9
+ /** Uniform starting edge colors; widths and radii may differ. */
10
+ borderColor?: ColorKeyframes;
11
+ color?: ColorKeyframes;
7
12
  opacity?: KeyframeValue;
8
13
  /** Same as `translateX`, in DIPs. */
9
14
  x?: KeyframeValue;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAE7D,+GAA+G;AAC/G,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAA;AAE/D,MAAM,WAAW,SAAS;IACxB,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,qCAAqC;IACrC,CAAC,CAAC,EAAE,aAAa,CAAA;IACjB,qCAAqC;IACrC,CAAC,CAAC,EAAE,aAAa,CAAA;IACjB,2CAA2C;IAC3C,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,MAAM,CAAC,EAAE,aAAa,CAAA;IACtB,MAAM,CAAC,EAAE,aAAa,CAAA;IACtB,eAAe;IACf,MAAM,CAAC,EAAE,aAAa,CAAA;IACtB,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,OAAO,CAAC,EAAE,aAAa,CAAA;CACxB;AAED,+FAA+F;AAC/F,MAAM,MAAM,MAAM,GAChB,IAAI,GAAG;IAAE,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAA;CAAE,CAAA;AAEzE,yFAAyF;AACzF,MAAM,WAAW,aAAa;IAC5B,CAAC,CAAC,EAAE,MAAM,CAAA;IACV,CAAC,CAAC,EAAE,MAAM,CAAA;CACX;AAED,MAAM,WAAW,YAAY;IAC3B,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mFAAmF;IACnF,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG;QAAE,CAAC,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAAC,CAAC,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,CAAA;CAC5E;AAED,MAAM,WAAW,OAAO;IACtB,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAA;IACZ,iCAAiC;IACjC,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAA;CAC5C;AAED,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG;KACvC,CAAC,IAAI,MAAM,SAAS,CAAC,CAAC,EAAE,UAAU,GAAG,UAAU;CACjD,GAAG;IACF,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,iFAAiF;IACjF,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IAC1B,mFAAmF;IACnF,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEhD,+GAA+G;AAC/G,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAA;AAE/D,MAAM,WAAW,SAAS;IACxB,eAAe,CAAC,EAAE,cAAc,CAAA;IAChC,iEAAiE;IACjE,WAAW,CAAC,EAAE,cAAc,CAAA;IAC5B,KAAK,CAAC,EAAE,cAAc,CAAA;IACtB,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,qCAAqC;IACrC,CAAC,CAAC,EAAE,aAAa,CAAA;IACjB,qCAAqC;IACrC,CAAC,CAAC,EAAE,aAAa,CAAA;IACjB,2CAA2C;IAC3C,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,MAAM,CAAC,EAAE,aAAa,CAAA;IACtB,MAAM,CAAC,EAAE,aAAa,CAAA;IACtB,eAAe;IACf,MAAM,CAAC,EAAE,aAAa,CAAA;IACtB,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,OAAO,CAAC,EAAE,aAAa,CAAA;CACxB;AAED,+FAA+F;AAC/F,MAAM,MAAM,MAAM,GAChB,IAAI,GAAG;IAAE,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAA;CAAE,CAAA;AAEzE,yFAAyF;AACzF,MAAM,WAAW,aAAa;IAC5B,CAAC,CAAC,EAAE,MAAM,CAAA;IACV,CAAC,CAAC,EAAE,MAAM,CAAA;CACX;AAED,MAAM,WAAW,YAAY;IAC3B,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mFAAmF;IACnF,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG;QAAE,CAAC,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAAC,CAAC,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,CAAA;CAC5E;AAED,MAAM,WAAW,OAAO;IACtB,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAA;IACZ,iCAAiC;IACjC,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAA;CAC5C;AAED,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG;KACvC,CAAC,IAAI,MAAM,SAAS,CAAC,CAAC,EAAE,UAAU,GAAG,UAAU;CACjD,GAAG;IACF,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,iFAAiF;IACjF,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IAC1B,mFAAmF;IACnF,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rafaelaugusto/jolt",
3
- "version": "0.1.0-beta.3",
3
+ "version": "0.1.0-beta.4",
4
4
  "description": "Native animations for NativeScript: presets, springs, keyframes and stagger in one call.",
5
5
  "keywords": [
6
6
  "nativescript",
@@ -32,7 +32,10 @@
32
32
  },
33
33
  "files": [
34
34
  "dist",
35
- "src"
35
+ "src",
36
+ "platforms/android/java",
37
+ "platforms/android/include.gradle",
38
+ "platforms/android/proguard-rules.pro"
36
39
  ],
37
40
  "sideEffects": false,
38
41
  "scripts": {
@@ -66,5 +69,11 @@
66
69
  },
67
70
  "publishConfig": {
68
71
  "access": "public"
72
+ },
73
+ "nativescript": {
74
+ "platforms": {
75
+ "android": "8.9.0",
76
+ "ios": "8.9.0"
77
+ }
69
78
  }
70
79
  }
@@ -0,0 +1,7 @@
1
+ // Applied by the NativeScript app. Preserve fields used by the native color target.
2
+ def joltDependency = nativescriptDependencies.find { it.name == '@rafaelaugusto/jolt' }
3
+ if (joltDependency != null) {
4
+ android.buildTypes.all {
5
+ proguardFile new File(rootDir, "${joltDependency.directory}/platforms/android/proguard-rules.pro")
6
+ }
7
+ }
@@ -0,0 +1,102 @@
1
+ package org.rafaelaugusto.jolt;
2
+
3
+ import android.graphics.PorterDuff;
4
+ import android.graphics.drawable.ColorDrawable;
5
+ import android.graphics.drawable.Drawable;
6
+ import android.view.View;
7
+ import android.widget.TextView;
8
+ import java.lang.ref.WeakReference;
9
+ import java.lang.reflect.Field;
10
+ import java.lang.reflect.Method;
11
+
12
+ /** ObjectAnimator target. All frame updates stay in native code. */
13
+ public final class ColorTarget {
14
+ private static final String BORDER = "org.nativescript.widgets.BorderDrawable";
15
+ private static final Field[] FIELDS = fields();
16
+ private static final String SHADOW = "org.nativescript.widgets.BoxShadowDrawable";
17
+ private static final Method UNWRAP = unwrapMethod();
18
+ private final WeakReference<View> view;
19
+ private final int property;
20
+ private int color;
21
+
22
+ // 0 = background, 1 = uniform border, 2 = text.
23
+ public ColorTarget(View view, int property, int initialColor) {
24
+ this.view = new WeakReference<>(view);
25
+ this.property = property;
26
+ this.color = initialColor;
27
+ }
28
+
29
+ private static Field[] fields() {
30
+ try {
31
+ Class<?> type = Class.forName(BORDER);
32
+ String[] names = {"backgroundColor", "borderTopColor", "borderRightColor", "borderBottomColor", "borderLeftColor"};
33
+ Field[] result = new Field[names.length];
34
+ for (int i = 0; i < names.length; i++) {
35
+ result[i] = type.getDeclaredField(names[i]);
36
+ result[i].setAccessible(true);
37
+ }
38
+ return result;
39
+ } catch (ReflectiveOperationException | SecurityException exception) {
40
+ return null;
41
+ }
42
+ }
43
+
44
+ private static Method unwrapMethod() {
45
+ try {
46
+ return Class.forName(SHADOW).getMethod("getWrappedDrawable");
47
+ } catch (ReflectiveOperationException exception) {
48
+ return null;
49
+ }
50
+ }
51
+
52
+ private Drawable background(View nativeView) {
53
+ Drawable drawable = nativeView.getBackground();
54
+ // Avoid reflective method lookups and exceptions in the frame loop.
55
+ if (drawable != null && drawable.getClass().getName().equals(SHADOW) && UNWRAP != null) {
56
+ try {
57
+ return (Drawable) UNWRAP.invoke(drawable);
58
+ } catch (ReflectiveOperationException exception) {
59
+ return drawable;
60
+ }
61
+ }
62
+ return drawable;
63
+ }
64
+
65
+ public boolean isSupported() {
66
+ View nativeView = view.get();
67
+ if (nativeView == null) return false;
68
+ if (property == 2) return nativeView instanceof TextView;
69
+ Drawable drawable = background(nativeView);
70
+ if (drawable == null) return true;
71
+ if (drawable.getClass().getName().equals(SHADOW)) return false;
72
+ return !drawable.getClass().getName().equals(BORDER) || FIELDS != null;
73
+ }
74
+
75
+ public int getColor() { return color; }
76
+
77
+ public void setColor(int value) {
78
+ View nativeView = view.get();
79
+ if (nativeView == null) return;
80
+ color = value;
81
+ if (property == 2) {
82
+ ((TextView) nativeView).setTextColor(value);
83
+ return;
84
+ }
85
+ // Resolve on every frame: a Core style update may replace the drawable.
86
+ Drawable drawable = background(nativeView);
87
+ if (drawable == null || drawable.getClass().getName().equals(SHADOW)) return;
88
+ if (drawable.getClass().getName().equals(BORDER)) {
89
+ if (FIELDS == null) return;
90
+ try {
91
+ if (property == 0) FIELDS[0].setInt(drawable, value);
92
+ else for (int i = 1; i < FIELDS.length; i++) FIELDS[i].setInt(drawable, value);
93
+ drawable.invalidateSelf();
94
+ } catch (IllegalAccessException exception) {
95
+ // Unsupported Core versions keep the final model color.
96
+ }
97
+ } else if (property == 0) {
98
+ if (drawable instanceof ColorDrawable) ((ColorDrawable) drawable).setColor(value);
99
+ else drawable.setColorFilter(value, PorterDuff.Mode.SRC_IN);
100
+ }
101
+ }
102
+ }
@@ -0,0 +1,6 @@
1
+ -keep class org.rafaelaugusto.jolt.ColorTarget { *; }
2
+ -keep class org.nativescript.widgets.BorderDrawable { *; }
3
+ -keepnames class org.nativescript.widgets.BoxShadowDrawable
4
+ -keepclassmembers class org.nativescript.widgets.BoxShadowDrawable {
5
+ public android.graphics.drawable.Drawable getWrappedDrawable();
6
+ }
package/src/animate.ts CHANGED
@@ -1,4 +1,10 @@
1
1
  import { View } from '@nativescript/core'
2
+ import {
3
+ isColorProperty,
4
+ parseColor,
5
+ validateColorTarget,
6
+ type ColorKeyframes,
7
+ } from './color.js'
2
8
  import { DEFAULT_FRICTION, solveDecay } from './decay.js'
3
9
  import {
4
10
  arrivalOf,
@@ -59,14 +65,20 @@ export function animate(
59
65
  const feedback = resolveHaptic(haptic)
60
66
 
61
67
  if (!isList(target)) {
68
+ const view = toView(target)
69
+ for (const property of Object.keys(keyframes))
70
+ if (isColorProperty(property)) validateColorTarget(view, property)
62
71
  return new ViewAnimation(
63
- toView(target),
72
+ view,
64
73
  plan(keyframes, base, overrides, 0, relative),
65
74
  feedback,
66
75
  )
67
76
  }
68
77
 
69
78
  const views = target.map(toView)
79
+ for (const view of views)
80
+ for (const property of Object.keys(keyframes))
81
+ if (isColorProperty(property)) validateColorTarget(view, property)
70
82
  return group(
71
83
  views.map(
72
84
  (view, index) =>
@@ -282,7 +294,10 @@ export function startingValues(
282
294
  const { keyframes, relative } = prepare(animation, options)
283
295
  if (!keyframes) throw new Error(`Unknown preset "${animation}".`)
284
296
  return new Map(
285
- expand(keyframes).map(([property, [first]]) => [property, relative ? null : first!]),
297
+ expand(keyframes).map(([property, [first]]) => [
298
+ property,
299
+ relative && !isColorProperty(property) ? null : first!,
300
+ ]),
286
301
  )
287
302
  }
288
303
 
@@ -290,7 +305,7 @@ function expand(keyframes: Keyframes): [Property, (number | null)[]][] {
290
305
  const expanded: [Property, (number | null)[]][] = []
291
306
  for (const [key, value] of Object.entries(keyframes) as [
292
307
  keyof Keyframes,
293
- KeyframeValue | undefined,
308
+ KeyframeValue | ColorKeyframes | undefined,
294
309
  ][]) {
295
310
  if (value === undefined) continue
296
311
  if (key !== 'scale' && !isProperty(key)) throw new Error(`Can't animate "${key}".`)
@@ -304,7 +319,18 @@ function expand(keyframes: Keyframes): [Property, (number | null)[]][] {
304
319
  return expanded
305
320
  }
306
321
 
307
- function toKeyframes(property: Property, value: KeyframeValue): (number | null)[] {
322
+ function toKeyframes(
323
+ property: Property,
324
+ value: KeyframeValue | ColorKeyframes,
325
+ ): (number | null)[] {
326
+ if (isColorProperty(property)) {
327
+ const frames = Array.isArray(value) ? [...value] : [null, value]
328
+ if (!frames.length) throw new TypeError(`Empty color keyframes for "${property}".`)
329
+ if (frames.length === 1) frames.unshift(null)
330
+ return frames.map((frame) => (frame === null ? null : parseColor(frame)))
331
+ }
332
+ if (typeof value !== 'number' && !Array.isArray(value))
333
+ throw new TypeError(`Invalid numeric keyframes for "${property}".`)
308
334
  const frames = typeof value === 'number' ? [null, value] : [...value]
309
335
  if (frames.length === 1) frames.unshift(null)
310
336
  if (
package/src/color.ts ADDED
@@ -0,0 +1,101 @@
1
+ import { Button, Color, Label, TextBase, isIOS, type View } from '@nativescript/core'
2
+
3
+ export type ColorProperty = 'backgroundColor' | 'borderColor' | 'color'
4
+ export type ColorValue = string | Color
5
+ export type ColorKeyframes = ColorValue | readonly (ColorValue | null)[]
6
+ export const isColorProperty = (property: string): property is ColorProperty =>
7
+ property === 'backgroundColor' || property === 'borderColor' || property === 'color'
8
+
9
+ export function parseColor(value: unknown): number {
10
+ if (value instanceof Color) return value.argb >>> 0
11
+ if (typeof value !== 'string' || !Color.isValid(value))
12
+ throw new TypeError(`Invalid animation color: ${String(value)}.`)
13
+ return new Color(value).argb >>> 0
14
+ }
15
+
16
+ export function readColor(view: View, property: ColorProperty): number {
17
+ const value = property === 'borderColor' ? view.borderTopColor : view[property]
18
+ return value instanceof Color
19
+ ? value.argb >>> 0
20
+ : property === 'backgroundColor'
21
+ ? 0
22
+ : 0xff000000
23
+ }
24
+
25
+ export function validateColorTarget(view: View, property: ColorProperty) {
26
+ if (
27
+ property === 'color' &&
28
+ !(isIOS ? view instanceof Label || view instanceof Button : view instanceof TextBase)
29
+ )
30
+ throw new TypeError(
31
+ 'Text color animation requires a Label or Button on iOS, or a TextBase on Android.',
32
+ )
33
+ if (property === 'borderColor') {
34
+ const colors = [
35
+ view.borderTopColor,
36
+ view.borderRightColor,
37
+ view.borderBottomColor,
38
+ view.borderLeftColor,
39
+ ]
40
+ if (
41
+ colors.some(
42
+ (color) => (color?.argb ?? 0xff000000) !== (colors[0]?.argb ?? 0xff000000),
43
+ )
44
+ )
45
+ throw new TypeError(
46
+ 'borderColor animation requires the same starting color on every edge.',
47
+ )
48
+ }
49
+ }
50
+
51
+ const clamp = (value: number) => Math.max(0, Math.min(1, value))
52
+ const linear = (channel: number) =>
53
+ channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4
54
+ const encoded = (channel: number) =>
55
+ channel <= 0.0031308 ? 12.92 * channel : 1.055 * channel ** (1 / 2.4) - 0.055
56
+
57
+ function lab(value: number) {
58
+ const r = linear(((value >>> 16) & 255) / 255)
59
+ const g = linear(((value >>> 8) & 255) / 255)
60
+ const b = linear((value & 255) / 255)
61
+ const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b)
62
+ const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b)
63
+ const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b)
64
+ return [
65
+ 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
66
+ 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
67
+ 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,
68
+ ] as const
69
+ }
70
+
71
+ /** Premultiplied OKLab interpolation, clipped to the sRGB output gamut. */
72
+ export function colorMixer(from: number, to: number): (progress: number) => number {
73
+ const a = lab(from),
74
+ b = lab(to)
75
+ const alphaA = (from >>> 24) / 255,
76
+ alphaB = (to >>> 24) / 255
77
+ return (progress) => {
78
+ const p = clamp(progress)
79
+ if (p === 0) return from
80
+ if (p === 1) return to
81
+ const alpha = alphaA + (alphaB - alphaA) * p
82
+ if (alpha <= 0) return 0
83
+ const channel = (i: number) => (a[i]! * alphaA * (1 - p) + b[i]! * alphaB * p) / alpha
84
+ const L = channel(0),
85
+ A = channel(1),
86
+ B = channel(2)
87
+ const l = (L + 0.3963377774 * A + 0.2158037573 * B) ** 3
88
+ const m = (L - 0.1055613458 * A - 0.0638541728 * B) ** 3
89
+ const s = (L - 0.0894841775 * A - 1.291485548 * B) ** 3
90
+ const r = Math.round(
91
+ clamp(encoded(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s)) * 255,
92
+ )
93
+ const g = Math.round(
94
+ clamp(encoded(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s)) * 255,
95
+ )
96
+ const blue = Math.round(
97
+ clamp(encoded(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s)) * 255,
98
+ )
99
+ return ((Math.round(alpha * 255) << 24) | (r << 16) | (g << 8) | blue) >>> 0
100
+ }
101
+ }
@@ -0,0 +1,168 @@
1
+ import { type View } from '@nativescript/core'
2
+ import { readColor, type ColorProperty } from '../color.js'
3
+ import type { Track } from '../track.js'
4
+
5
+ interface NativeTarget {
6
+ getColor(): number
7
+ setColor(value: number): void
8
+ isSupported(): boolean
9
+ }
10
+ interface ColorHandle {
11
+ cancel(): void
12
+ elapsed(): number
13
+ target: NativeTarget
14
+ }
15
+ const playing = new WeakMap<View, Map<ColorProperty, ColorHandle>>()
16
+
17
+ export function presentedAndroidColor(view: View, property: ColorProperty): number {
18
+ const active = playing.get(view)?.get(property)
19
+ if (active) return active.target.getColor() >>> 0
20
+ if (property === 'color' && view.nativeViewProtected instanceof android.widget.TextView)
21
+ return view.nativeViewProtected.getCurrentTextColor() >>> 0
22
+ return readColor(view, property)
23
+ }
24
+
25
+ export function playAndroidColor(
26
+ view: View,
27
+ track: Track,
28
+ onEnd: (finished?: boolean) => void,
29
+ ): ColorHandle {
30
+ const property = track.property as ColorProperty
31
+ const changes =
32
+ property === 'borderColor'
33
+ ? [
34
+ 'borderTopColorChange',
35
+ 'borderRightColorChange',
36
+ 'borderBottomColorChange',
37
+ 'borderLeftColorChange',
38
+ ]
39
+ : [`${property}Change`]
40
+ const bridge = globalThis as unknown as {
41
+ org: {
42
+ rafaelaugusto: {
43
+ jolt: {
44
+ ColorTarget: new (
45
+ view: android.view.View,
46
+ property: number,
47
+ color: number,
48
+ ) => NativeTarget
49
+ }
50
+ }
51
+ }
52
+ }
53
+ let target: NativeTarget
54
+ try {
55
+ target = new bridge.org.rafaelaugusto.jolt.ColorTarget(
56
+ view.nativeViewProtected,
57
+ property === 'backgroundColor' ? 0 : property === 'borderColor' ? 1 : 2,
58
+ track.values[0]! | 0,
59
+ )
60
+ } catch {
61
+ // A JS-only update cannot install the native plugin. Keep the final model.
62
+ onEnd(false)
63
+ return {
64
+ cancel() {},
65
+ elapsed: () => 0,
66
+ target: {
67
+ getColor: () => readColor(view, property),
68
+ setColor() {},
69
+ isSupported: () => false,
70
+ },
71
+ }
72
+ }
73
+ const frames = Array.create(android.animation.Keyframe, track.values.length)
74
+ track.values.forEach((value, index) => {
75
+ frames[index] = android.animation.Keyframe.ofInt(track.times[index]!, value | 0)
76
+ })
77
+ const holder = android.animation.PropertyValuesHolder.ofKeyframe('color', frames)
78
+ holder.setEvaluator(new android.animation.ArgbEvaluator())
79
+ const holders = Array.create(android.animation.PropertyValuesHolder, 1)
80
+ holders[0] = holder
81
+ const animator = android.animation.ObjectAnimator.ofPropertyValuesHolder(
82
+ target as unknown as java.lang.Object,
83
+ holders,
84
+ )
85
+ animator.setDuration(Math.max(track.duration, 1))
86
+ animator.setStartDelay(track.delay)
87
+ animator.setInterpolator(new android.view.animation.LinearInterpolator())
88
+ if (track.repeat > 0) {
89
+ animator.setRepeatCount(
90
+ Number.isFinite(track.repeat)
91
+ ? track.repeat
92
+ : android.animation.ValueAnimator.INFINITE,
93
+ )
94
+ animator.setRepeatMode(
95
+ track.reverse
96
+ ? android.animation.ValueAnimator.REVERSE
97
+ : android.animation.ValueAnimator.RESTART,
98
+ )
99
+ }
100
+ let stopped = false,
101
+ started = false,
102
+ queued = false
103
+ const cleanup = () => {
104
+ if (stopped) return
105
+ stopped = true
106
+ view.off('unloaded', cancel)
107
+ if (property === 'color') {
108
+ view.off('textChange', invalidate)
109
+ view.off('formattedTextChange', invalidate)
110
+ }
111
+ for (const name of changes) view.style.off(name, invalidate)
112
+ if (playing.get(view)?.get(property) === handle) playing.get(view)?.delete(property)
113
+ }
114
+ const cancel = () => {
115
+ if (stopped) return
116
+ cleanup()
117
+ animator.cancel()
118
+ onEnd(false)
119
+ }
120
+ const invalidate = () => {
121
+ if (stopped || queued) return
122
+ queued = true
123
+ void Promise.resolve().then(() => {
124
+ queued = false
125
+ if (!stopped) cancel()
126
+ })
127
+ }
128
+ const handle: ColorHandle = {
129
+ target,
130
+ elapsed: () => (started ? track.delay + animator.getCurrentPlayTime() : 0),
131
+ cancel() {
132
+ cleanup()
133
+ animator.cancel()
134
+ },
135
+ }
136
+ let entries = playing.get(view)
137
+ if (!entries) playing.set(view, (entries = new Map()))
138
+ entries.set(property, handle)
139
+ animator.addListener(
140
+ new android.animation.Animator.AnimatorListener({
141
+ onAnimationStart() {
142
+ started = true
143
+ },
144
+ onAnimationRepeat() {},
145
+ onAnimationCancel() {},
146
+ onAnimationEnd() {
147
+ if (!stopped) {
148
+ cleanup()
149
+ onEnd()
150
+ }
151
+ },
152
+ }),
153
+ )
154
+ view.on('unloaded', cancel)
155
+ if (property === 'color') {
156
+ view.on('textChange', invalidate)
157
+ view.on('formattedTextChange', invalidate)
158
+ }
159
+ for (const name of changes) view.style.on(name, invalidate)
160
+ if (target.isSupported()) {
161
+ target.setColor(track.values[0]! | 0)
162
+ animator.start()
163
+ } else {
164
+ cleanup()
165
+ onEnd(false)
166
+ }
167
+ return handle
168
+ }
@@ -1,6 +1,8 @@
1
1
  import { Utils, type View } from '@nativescript/core'
2
- import type { Property } from '../properties.js'
2
+ import type { NumericProperty } from '../properties.js'
3
3
  import type { Driver } from './index.js'
4
+ import { isColorProperty } from '../color.js'
5
+ import { playAndroidColor, presentedAndroidColor } from './android-color.js'
4
6
 
5
7
  type NativeView = android.view.View
6
8
 
@@ -44,7 +46,7 @@ function fadeLayer(view: View, nativeView: NativeView): (() => void) | undefined
44
46
  }
45
47
 
46
48
  const accessors: Record<
47
- Property,
49
+ NumericProperty,
48
50
  { get(view: NativeView): number; set(view: NativeView, value: number): void }
49
51
  > = {
50
52
  opacity: { get: (v) => v.getAlpha(), set: (v, value) => v.setAlpha(value) },
@@ -57,7 +59,7 @@ const accessors: Record<
57
59
  rotateY: { get: (v) => v.getRotationY(), set: (v, value) => v.setRotationY(value) },
58
60
  }
59
61
 
60
- const unit = (property: Property) =>
62
+ const unit = (property: NumericProperty) =>
61
63
  property === 'x' || property === 'y' ? Utils.layout.getDisplayDensity() : 1
62
64
 
63
65
  export function createAndroidDriver(): Driver {
@@ -71,13 +73,14 @@ export function createAndroidDriver(): Driver {
71
73
  rotate: View.ROTATION,
72
74
  rotateX: View.ROTATION_X,
73
75
  rotateY: View.ROTATION_Y,
74
- } satisfies Record<Property, unknown>
76
+ } satisfies Record<NumericProperty, unknown>
75
77
  const interpolator = new android.view.animation.LinearInterpolator()
76
78
 
77
79
  return {
78
80
  now: () => android.os.SystemClock.uptimeMillis(),
79
81
 
80
82
  play(view, track, _startedAt, onEnd) {
83
+ if (isColorProperty(track.property)) return playAndroidColor(view, track, onEnd)
81
84
  const nativeView = view.nativeViewProtected as NativeView
82
85
  const scale = unit(track.property)
83
86
 
@@ -170,6 +173,7 @@ export function createAndroidDriver(): Driver {
170
173
  elapsed: (handle) => (handle as Handle).elapsed(),
171
174
 
172
175
  presented(view, property) {
176
+ if (isColorProperty(property)) return presentedAndroidColor(view, property)
173
177
  const nativeView = view.nativeViewProtected as NativeView | undefined
174
178
  return nativeView ? accessors[property].get(nativeView) / unit(property) : undefined
175
179
  },
@@ -8,7 +8,12 @@ export interface Driver {
8
8
  /** Milliseconds, on the clock the platform's animations run on. */
9
9
  now(): number
10
10
  /** Starts the track at `startedAt + track.delay` and calls `onEnd` once, however it ends. */
11
- play(view: View, track: Track, startedAt: number, onEnd: () => void): unknown
11
+ play(
12
+ view: View,
13
+ track: Track,
14
+ startedAt: number,
15
+ onEnd: (finished?: boolean) => void,
16
+ ): unknown
12
17
  stop(view: View, handle: unknown): void
13
18
  /** Track time, including its delay, when the native clock differs from `now()`. */
14
19
  elapsed?(handle: unknown): number