react-native-laminar 1.4.5 → 1.5.0

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 (102) hide show
  1. package/README.md +36 -6
  2. package/lib/commonjs/hooks/use-leading-animation.js +49 -0
  3. package/lib/commonjs/hooks/use-leading-animation.js.map +1 -0
  4. package/lib/commonjs/hooks/use-morph-motion.js +3 -1
  5. package/lib/commonjs/hooks/use-morph-motion.js.map +1 -1
  6. package/lib/commonjs/index.js +35 -6
  7. package/lib/commonjs/index.js.map +1 -1
  8. package/lib/commonjs/motion/preset-map.js +5 -0
  9. package/lib/commonjs/motion/preset-map.js.map +1 -1
  10. package/lib/commonjs/view/leading-element.js +37 -0
  11. package/lib/commonjs/view/leading-element.js.map +1 -0
  12. package/lib/commonjs/view/morph-viewport.js +26 -0
  13. package/lib/commonjs/view/morph-viewport.js.map +1 -1
  14. package/lib/commonjs/view/number-run.js +70 -22
  15. package/lib/commonjs/view/number-run.js.map +1 -1
  16. package/lib/commonjs/view/slot-shadow.js +77 -0
  17. package/lib/commonjs/view/slot-shadow.js.map +1 -0
  18. package/lib/commonjs/view/slots-run.js +111 -42
  19. package/lib/commonjs/view/slots-run.js.map +1 -1
  20. package/lib/commonjs/view/text-run.js +13 -24
  21. package/lib/commonjs/view/text-run.js.map +1 -1
  22. package/lib/commonjs/view/ticker-run.js +127 -0
  23. package/lib/commonjs/view/ticker-run.js.map +1 -0
  24. package/lib/module/hooks/use-leading-animation.js +44 -0
  25. package/lib/module/hooks/use-leading-animation.js.map +1 -0
  26. package/lib/module/hooks/use-morph-motion.js +3 -1
  27. package/lib/module/hooks/use-morph-motion.js.map +1 -1
  28. package/lib/module/index.js +35 -6
  29. package/lib/module/index.js.map +1 -1
  30. package/lib/module/motion/preset-map.js +5 -0
  31. package/lib/module/motion/preset-map.js.map +1 -1
  32. package/lib/module/view/leading-element.js +32 -0
  33. package/lib/module/view/leading-element.js.map +1 -0
  34. package/lib/module/view/morph-viewport.js +28 -2
  35. package/lib/module/view/morph-viewport.js.map +1 -1
  36. package/lib/module/view/number-run.js +70 -23
  37. package/lib/module/view/number-run.js.map +1 -1
  38. package/lib/module/view/slot-shadow.js +72 -0
  39. package/lib/module/view/slot-shadow.js.map +1 -0
  40. package/lib/module/view/slots-run.js +111 -42
  41. package/lib/module/view/slots-run.js.map +1 -1
  42. package/lib/module/view/text-run.js +14 -25
  43. package/lib/module/view/text-run.js.map +1 -1
  44. package/lib/module/view/ticker-run.js +121 -0
  45. package/lib/module/view/ticker-run.js.map +1 -0
  46. package/lib/typescript/commonjs/hooks/use-leading-animation.d.ts +16 -0
  47. package/lib/typescript/commonjs/hooks/use-leading-animation.d.ts.map +1 -0
  48. package/lib/typescript/commonjs/hooks/use-morph-motion.d.ts +3 -2
  49. package/lib/typescript/commonjs/hooks/use-morph-motion.d.ts.map +1 -1
  50. package/lib/typescript/commonjs/index.d.ts +1 -1
  51. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  52. package/lib/typescript/commonjs/motion/preset-map.d.ts.map +1 -1
  53. package/lib/typescript/commonjs/types.d.ts +14 -2
  54. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  55. package/lib/typescript/commonjs/view/leading-element.d.ts +14 -0
  56. package/lib/typescript/commonjs/view/leading-element.d.ts.map +1 -0
  57. package/lib/typescript/commonjs/view/morph-viewport.d.ts +4 -1
  58. package/lib/typescript/commonjs/view/morph-viewport.d.ts.map +1 -1
  59. package/lib/typescript/commonjs/view/number-run.d.ts +6 -2
  60. package/lib/typescript/commonjs/view/number-run.d.ts.map +1 -1
  61. package/lib/typescript/commonjs/view/slot-shadow.d.ts +11 -0
  62. package/lib/typescript/commonjs/view/slot-shadow.d.ts.map +1 -0
  63. package/lib/typescript/commonjs/view/slots-run.d.ts +8 -3
  64. package/lib/typescript/commonjs/view/slots-run.d.ts.map +1 -1
  65. package/lib/typescript/commonjs/view/text-run.d.ts.map +1 -1
  66. package/lib/typescript/commonjs/view/ticker-run.d.ts +21 -0
  67. package/lib/typescript/commonjs/view/ticker-run.d.ts.map +1 -0
  68. package/lib/typescript/module/hooks/use-leading-animation.d.ts +16 -0
  69. package/lib/typescript/module/hooks/use-leading-animation.d.ts.map +1 -0
  70. package/lib/typescript/module/hooks/use-morph-motion.d.ts +3 -2
  71. package/lib/typescript/module/hooks/use-morph-motion.d.ts.map +1 -1
  72. package/lib/typescript/module/index.d.ts +1 -1
  73. package/lib/typescript/module/index.d.ts.map +1 -1
  74. package/lib/typescript/module/motion/preset-map.d.ts.map +1 -1
  75. package/lib/typescript/module/types.d.ts +14 -2
  76. package/lib/typescript/module/types.d.ts.map +1 -1
  77. package/lib/typescript/module/view/leading-element.d.ts +14 -0
  78. package/lib/typescript/module/view/leading-element.d.ts.map +1 -0
  79. package/lib/typescript/module/view/morph-viewport.d.ts +4 -1
  80. package/lib/typescript/module/view/morph-viewport.d.ts.map +1 -1
  81. package/lib/typescript/module/view/number-run.d.ts +6 -2
  82. package/lib/typescript/module/view/number-run.d.ts.map +1 -1
  83. package/lib/typescript/module/view/slot-shadow.d.ts +11 -0
  84. package/lib/typescript/module/view/slot-shadow.d.ts.map +1 -0
  85. package/lib/typescript/module/view/slots-run.d.ts +8 -3
  86. package/lib/typescript/module/view/slots-run.d.ts.map +1 -1
  87. package/lib/typescript/module/view/text-run.d.ts.map +1 -1
  88. package/lib/typescript/module/view/ticker-run.d.ts +21 -0
  89. package/lib/typescript/module/view/ticker-run.d.ts.map +1 -0
  90. package/package.json +3 -1
  91. package/src/hooks/use-leading-animation.ts +74 -0
  92. package/src/hooks/use-morph-motion.ts +10 -1
  93. package/src/index.tsx +44 -6
  94. package/src/motion/preset-map.ts +5 -0
  95. package/src/types.ts +17 -2
  96. package/src/view/leading-element.tsx +45 -0
  97. package/src/view/morph-viewport.tsx +39 -1
  98. package/src/view/number-run.tsx +94 -26
  99. package/src/view/slot-shadow.tsx +99 -0
  100. package/src/view/slots-run.tsx +163 -58
  101. package/src/view/text-run.tsx +14 -51
  102. package/src/view/ticker-run.tsx +167 -0
@@ -1 +1 @@
1
- {"version":3,"file":"number-run.d.ts","sourceRoot":"","sources":["../../../../src/view/number-run.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA0B,MAAM,OAAO,CAAC;AAC/C,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAQ,MAAM,cAAc,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAe3D,KAAK,cAAc,GAAG;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAGF,eAAO,MAAM,SAAS,yGASjB,QAAQ,CAAC,cAAc,CAAC,uBA8C5B,CAAC"}
1
+ {"version":3,"file":"number-run.d.ts","sourceRoot":"","sources":["../../../../src/view/number-run.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAmB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC/D,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAQ,MAAM,cAAc,CAAC;AAIpE,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAgB3D,KAAK,cAAc,GAAG;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACtC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAGF,eAAO,MAAM,SAAS,iJAajB,QAAQ,CAAC,cAAc,CAAC,uBAuG5B,CAAC"}
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ type SlotShadowProps = {
3
+ readonly slotHeight: number;
4
+ readonly viewportHeight: number;
5
+ readonly color?: string;
6
+ readonly size?: number;
7
+ readonly children: React.ReactElement;
8
+ };
9
+ export declare const SlotShadow: React.NamedExoticComponent<SlotShadowProps>;
10
+ export {};
11
+ //# sourceMappingURL=slot-shadow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slot-shadow.d.ts","sourceRoot":"","sources":["../../../../src/view/slot-shadow.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAkD1B,KAAK,eAAe,GAAG;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAC;CACvC,CAAC;AAIF,eAAO,MAAM,UAAU,6CA+BrB,CAAC"}
@@ -1,6 +1,6 @@
1
- import React from "react";
1
+ import React, { type ReactNode } from "react";
2
2
  import { type StyleProp, type TextStyle } from "react-native";
3
- import type { LaminarAlign, MotionRecipe } from "../types";
3
+ import type { LaminarAlign, LaminarShadow, MotionRecipe } from "../types";
4
4
  type SlotsRunProps = {
5
5
  readonly value: string;
6
6
  readonly motionRecipe: MotionRecipe;
@@ -9,7 +9,12 @@ type SlotsRunProps = {
9
9
  readonly textStyle?: StyleProp<TextStyle>;
10
10
  readonly staggerMs: number;
11
11
  readonly className?: string;
12
+ readonly leading?: ReactNode;
13
+ readonly leadingKey?: string | number;
14
+ readonly leadingGap?: number;
15
+ readonly ready?: boolean;
16
+ readonly shadow?: LaminarShadow;
12
17
  };
13
- export declare const SlotsRun: React.MemoExoticComponent<({ value, motionRecipe, align, fontSize, textStyle, staggerMs, className, }: Readonly<SlotsRunProps>) => React.JSX.Element>;
18
+ export declare const SlotsRun: React.MemoExoticComponent<({ value, motionRecipe, align, fontSize, textStyle, staggerMs, className, leading, leadingKey, leadingGap, ready, shadow, }: Readonly<SlotsRunProps>) => React.JSX.Element>;
14
19
  export {};
15
20
  //# sourceMappingURL=slots-run.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"slots-run.d.ts","sourceRoot":"","sources":["../../../../src/view/slots-run.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAqC,MAAM,OAAO,CAAC;AAC1D,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,SAAS,EAEf,MAAM,cAAc,CAAC;AAQtB,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAwB,MAAM,UAAU,CAAC;AAmQjF,KAAK,aAAa,GAAG;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAGF,eAAO,MAAM,QAAQ,yGAShB,QAAQ,CAAC,aAAa,CAAC,uBAwF3B,CAAC"}
1
+ {"version":3,"file":"slots-run.d.ts","sourceRoot":"","sources":["../../../../src/view/slots-run.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAA8B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC1E,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,SAAS,EAEf,MAAM,cAAc,CAAC;AAStB,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,YAAY,EAEb,MAAM,UAAU,CAAC;AAqSlB,KAAK,aAAa,GAAG;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACtC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;CACjC,CAAC;AAGF,eAAO,MAAM,QAAQ,yJAchB,QAAQ,CAAC,aAAa,CAAC,uBA+I3B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"text-run.d.ts","sourceRoot":"","sources":["../../../../src/view/text-run.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkD,MAAM,OAAO,CAAC;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAOzD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAG3D,KAAK,YAAY,GAAG;IAClB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;IAC7B,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACtC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC1C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAGF,eAAO,MAAM,OAAO,gJAYf,YAAY,uBAsHhB,CAAC"}
1
+ {"version":3,"file":"text-run.d.ts","sourceRoot":"","sources":["../../../../src/view/text-run.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAwB,MAAM,OAAO,CAAC;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGzD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAG3D,KAAK,YAAY,GAAG;IAClB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;IAC7B,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACtC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC1C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAGF,eAAO,MAAM,OAAO,gJAYf,YAAY,uBAqFhB,CAAC"}
@@ -0,0 +1,21 @@
1
+ import React, { type ReactNode } from "react";
2
+ import { type StyleProp, type TextStyle } from "react-native";
3
+ import type { LaminarAlign, LaminarShadow, MotionRecipe } from "../types";
4
+ type TickerRunProps = {
5
+ readonly value: string;
6
+ readonly motionRecipe: MotionRecipe;
7
+ readonly align: LaminarAlign;
8
+ readonly fontSize?: number;
9
+ readonly textStyle?: StyleProp<TextStyle>;
10
+ readonly staggerMs: number;
11
+ readonly className?: string;
12
+ readonly leading?: ReactNode;
13
+ readonly leadingKey?: string | number;
14
+ readonly leadingGap?: number;
15
+ readonly ready?: boolean;
16
+ /** accepted for API compatibility; flow values stay crisp and unmasked */
17
+ readonly shadow?: LaminarShadow;
18
+ };
19
+ export declare const TickerRun: React.MemoExoticComponent<({ value, motionRecipe, align, textStyle, className, leading, leadingKey, leadingGap, ready, }: Readonly<TickerRunProps>) => React.JSX.Element>;
20
+ export {};
21
+ //# sourceMappingURL=ticker-run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ticker-run.d.ts","sourceRoot":"","sources":["../../../../src/view/ticker-run.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAU,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAQ,MAAM,cAAc,CAAC;AAKpE,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,YAAY,EACb,MAAM,UAAU,CAAC;AAsBlB,KAAK,cAAc,GAAG;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACtC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;CACjC,CAAC;AAKF,eAAO,MAAM,SAAS,4HAWjB,QAAQ,CAAC,cAAc,CAAC,uBAsG5B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-laminar",
3
- "version": "1.4.5",
3
+ "version": "1.5.0",
4
4
  "description": "A React Native component for morphing text and numbers with character-level identity.",
5
5
  "keywords": [
6
6
  "react-native",
@@ -50,6 +50,8 @@
50
50
  },
51
51
  "sideEffects": false,
52
52
  "peerDependencies": {
53
+ "@react-native-masked-view/masked-view": ">=0.3",
54
+ "expo-linear-gradient": ">=15",
53
55
  "react": ">=18",
54
56
  "react-native": ">=0.74",
55
57
  "react-native-reanimated": ">=3"
@@ -0,0 +1,74 @@
1
+ import { useLayoutEffect, useMemo, useRef, type ReactNode } from "react";
2
+ import { useSharedValue } from "react-native-reanimated";
3
+ import {
4
+ createLeadingEnterTransition,
5
+ createLeadingExitTransition,
6
+ } from "../motion/entry-exit-builders";
7
+ import type { MotionRecipe } from "../types";
8
+
9
+ type UseLeadingAnimationParams = {
10
+ readonly leading?: ReactNode;
11
+ readonly leadingKey?: string | number;
12
+ readonly leadingGap: number;
13
+ readonly motionRecipe: MotionRecipe;
14
+ };
15
+
16
+ // keep leading presence and replacement motion consistent across every content variant
17
+ export const useLeadingAnimation = ({
18
+ leading,
19
+ leadingKey,
20
+ leadingGap,
21
+ motionRecipe,
22
+ }: UseLeadingAnimationParams) => {
23
+ const lastLeadingPresenceRef = useRef(Boolean(leading));
24
+ const lastLeadingKeyRef = useRef(leadingKey);
25
+ const isLeadingSwap =
26
+ Boolean(leading) &&
27
+ lastLeadingPresenceRef.current &&
28
+ leadingKey !== lastLeadingKeyRef.current;
29
+ const isLeadingChange =
30
+ Boolean(leading) !== lastLeadingPresenceRef.current ||
31
+ (Boolean(leading) && leadingKey !== lastLeadingKeyRef.current);
32
+ const leadingSwapProgress = useSharedValue(0);
33
+
34
+ // update the worklet flag after commit so render never writes a shared value
35
+ useLayoutEffect(() => {
36
+ leadingSwapProgress.value = isLeadingSwap ? 1 : 0;
37
+ }, [isLeadingSwap, leadingSwapProgress]);
38
+
39
+ const elementEnterTransition = useMemo(
40
+ () =>
41
+ createLeadingEnterTransition({
42
+ durationMs: motionRecipe.durationMs,
43
+ easing: motionRecipe.easing,
44
+ }),
45
+ [motionRecipe.durationMs, motionRecipe.easing]
46
+ );
47
+ const elementExitTransition = useMemo(
48
+ () =>
49
+ createLeadingExitTransition({
50
+ durationMs: motionRecipe.durationMs,
51
+ easing: motionRecipe.easing,
52
+ leadingGap,
53
+ scale: leadingSwapProgress,
54
+ }),
55
+ [
56
+ leadingGap,
57
+ leadingSwapProgress,
58
+ motionRecipe.durationMs,
59
+ motionRecipe.easing,
60
+ ]
61
+ );
62
+
63
+ if (isLeadingChange) {
64
+ lastLeadingPresenceRef.current = Boolean(leading);
65
+ lastLeadingKeyRef.current = leadingKey;
66
+ }
67
+
68
+ return {
69
+ elementEnterTransition,
70
+ elementExitTransition,
71
+ isLeadingChange,
72
+ isLeadingSwap,
73
+ };
74
+ };
@@ -4,10 +4,12 @@ import type {
4
4
  MotionRecipe,
5
5
  MorphAnimationPresetName,
6
6
  MorphContentVariant,
7
+ MorphNumberMode,
7
8
  } from "../types";
8
9
 
9
10
  type Params = {
10
11
  readonly variant: MorphContentVariant;
12
+ readonly numberMode?: MorphNumberMode;
11
13
  readonly animationPreset?: MorphAnimationPresetName;
12
14
  readonly animationDuration?: number;
13
15
  readonly stagger: number;
@@ -21,13 +23,20 @@ type MorphMotion = {
21
23
  // resolve one motion recipe and one shared stagger unit for a render tree
22
24
  export const useMorphMotion = ({
23
25
  variant,
26
+ numberMode,
24
27
  animationPreset,
25
28
  animationDuration,
26
29
  stagger,
27
30
  }: Params): MorphMotion => {
31
+ const isTicker = variant === "number" && numberMode === "ticker";
28
32
  // numeric variants need a quicker default because their lanes update often
29
33
  const resolvedPreset =
30
- animationPreset ?? (variant === "text" ? "default" : "snappy");
34
+ animationPreset ??
35
+ (isTicker
36
+ ? "ticker"
37
+ : variant === "text"
38
+ ? "default"
39
+ : "snappy");
31
40
 
32
41
  const motionRecipe = useMemo(
33
42
  () => resolveMotionRecipe(resolvedPreset, animationDuration),
package/src/index.tsx CHANGED
@@ -11,6 +11,7 @@ import type { LaminarLeadingMap, MorphingTextProps } from "./types";
11
11
  import { MorphViewport } from "./view/morph-viewport";
12
12
  import { NumberRun } from "./view/number-run";
13
13
  import { SlotsRun } from "./view/slots-run";
14
+ import { TickerRun } from "./view/ticker-run";
14
15
  import { TextRun } from "./view/text-run";
15
16
 
16
17
  // choose the content variant and connect it to the shared viewport and measurement flow
@@ -24,12 +25,14 @@ export const Laminar = React.memo(function Laminar({
24
25
  leading,
25
26
  leadingKey,
26
27
  leadingGap = 0,
28
+ numberMode = "morph",
29
+ shadow = false,
27
30
  style,
28
31
  containerStyle,
29
32
  fontStyle,
30
33
  animationDuration,
31
34
  animationPreset,
32
- stagger = 0.02,
35
+ stagger,
33
36
  autoSize = true,
34
37
  clipToBounds = false,
35
38
  }: Readonly<MorphingTextProps>) {
@@ -40,11 +43,16 @@ export const Laminar = React.memo(function Laminar({
40
43
  ? leadingMap[resolvedValue]
41
44
  : (leading as React.ReactNode);
42
45
  const resolvedLeadingKey = leadingMap ? resolvedValue : leadingKey;
46
+ const resolvedStagger =
47
+ stagger ?? (variant === "number" && numberMode === "ticker" ? 0 : 0.02);
48
+ const useLayoutAutoSize =
49
+ autoSize && variant === "number" && numberMode === "ticker";
43
50
  const { motionRecipe, staggerMs } = useMorphMotion({
44
51
  variant,
52
+ numberMode,
45
53
  animationPreset,
46
54
  animationDuration,
47
- stagger,
55
+ stagger: resolvedStagger,
48
56
  });
49
57
  const { textStyle } = useMorphTextStyle({
50
58
  fontSize,
@@ -67,9 +75,9 @@ export const Laminar = React.memo(function Laminar({
67
75
  flattenedStyle?.letterSpacing,
68
76
  flattenedStyle?.lineHeight,
69
77
  flattenedStyle?.textTransform,
70
- variant === "text" && Boolean(resolvedLeading),
71
- variant === "text" ? resolvedLeadingKey : undefined,
72
- variant === "text" ? leadingGap : 0,
78
+ Boolean(resolvedLeading),
79
+ resolvedLeadingKey,
80
+ leadingGap,
73
81
  ]);
74
82
  }, [
75
83
  leadingGap,
@@ -109,6 +117,9 @@ export const Laminar = React.memo(function Laminar({
109
117
  align={align}
110
118
  containerStyle={containerStyle}
111
119
  animatedWidthStyle={animatedWidthStyle}
120
+ autoSizeLayoutTransition={
121
+ useLayoutAutoSize ? motionRecipe.layoutTransition : undefined
122
+ }
112
123
  onVisibleLayout={autoSize ? captureVisibleLayout : undefined}
113
124
  measurement={
114
125
  shouldMeasure ? (
@@ -117,7 +128,7 @@ export const Laminar = React.memo(function Laminar({
117
128
  onLayout={captureLayout}
118
129
  style={{ flexDirection: "row", alignSelf: "flex-start" }}
119
130
  >
120
- {variant === "text" && resolvedLeading ? (
131
+ {resolvedLeading ? (
121
132
  <View
122
133
  style={{
123
134
  alignItems: "center",
@@ -146,6 +157,26 @@ export const Laminar = React.memo(function Laminar({
146
157
  textStyle={textStyle}
147
158
  staggerMs={staggerMs}
148
159
  className={className}
160
+ leading={resolvedLeading}
161
+ leadingKey={resolvedLeadingKey}
162
+ leadingGap={leadingGap}
163
+ ready={!autoSize || isAutoSizeReady}
164
+ shadow={shadow}
165
+ />
166
+ ) : variant === "number" && numberMode === "ticker" ? (
167
+ <TickerRun
168
+ value={resolvedValue}
169
+ motionRecipe={motionRecipe}
170
+ align={align}
171
+ fontSize={fontSize}
172
+ textStyle={textStyle}
173
+ staggerMs={staggerMs}
174
+ className={className}
175
+ leading={resolvedLeading}
176
+ leadingKey={resolvedLeadingKey}
177
+ leadingGap={leadingGap}
178
+ ready={!autoSize || isAutoSizeReady}
179
+ shadow={shadow}
149
180
  />
150
181
  ) : variant === "number" ? (
151
182
  <NumberRun
@@ -156,6 +187,10 @@ export const Laminar = React.memo(function Laminar({
156
187
  textStyle={textStyle}
157
188
  staggerMs={staggerMs}
158
189
  className={className}
190
+ leading={resolvedLeading}
191
+ leadingKey={resolvedLeadingKey}
192
+ leadingGap={leadingGap}
193
+ ready={!autoSize || isAutoSizeReady}
159
194
  />
160
195
  ) : (
161
196
  <TextRun
@@ -190,8 +225,11 @@ function isLeadingMap(
190
225
  export type {
191
226
  LaminarAlign,
192
227
  LaminarLeadingMap,
228
+ LaminarShadow,
229
+ LaminarShadowOptions,
193
230
  LaminarProps,
194
231
  MorphAnimationPresetName,
195
232
  MorphContentVariant,
233
+ MorphNumberMode,
196
234
  MorphingTextProps,
197
235
  } from "./types";
@@ -15,6 +15,7 @@ import type {
15
15
 
16
16
  // keep all presets on one curve family so layout and glyph motion agree
17
17
  const DEFAULT_CURVE = [0.19, 1, 0.22, 1] as const;
18
+ const TICKER_CURVE = [0.33, 1, 0.68, 1] as const;
18
19
 
19
20
  export const MOTION_PRESETS: Record<
20
21
  MorphAnimationPresetName,
@@ -39,6 +40,10 @@ export const MOTION_PRESETS: Record<
39
40
  duration: 0.5,
40
41
  bounce: 0.3,
41
42
  },
43
+ ticker: {
44
+ duration: 0.3,
45
+ ease: TICKER_CURVE,
46
+ },
42
47
  } as const;
43
48
 
44
49
  // the public preset table uses seconds while reanimated timing uses milliseconds
package/src/types.ts CHANGED
@@ -10,13 +10,24 @@ export type MorphAnimationPresetName =
10
10
  | "default"
11
11
  | "smooth"
12
12
  | "snappy"
13
- | "bouncy";
13
+ | "bouncy"
14
+ | "ticker";
14
15
 
15
16
  export type MorphContentVariant = "text" | "number" | "slots";
17
+ export type MorphNumberMode = "morph" | "ticker";
16
18
  export type LaminarAlign = "left" | "center" | "right";
17
19
  // text keys let leading content follow the same state as the displayed value
18
20
  export type LaminarLeadingMap = Readonly<Record<string, ReactNode>>;
19
21
 
22
+ export type LaminarShadowOptions = {
23
+ /** surface color used by the alpha mask when a platform requires it */
24
+ readonly color?: string;
25
+ /** fade depth in pixels; defaults to roughly one third of the viewport */
26
+ readonly size?: number;
27
+ };
28
+
29
+ export type LaminarShadow = boolean | LaminarShadowOptions;
30
+
20
31
  type CubicBezierTuple = readonly [number, number, number, number];
21
32
 
22
33
  export type MorphAnimationPreset =
@@ -52,16 +63,20 @@ export type MotionRecipe = {
52
63
  export type MorphingTextProps = {
53
64
  readonly text: string | number;
54
65
  readonly variant?: MorphContentVariant;
66
+ /** number-only renderer mode; ticker uses crisp in-place digit flow */
67
+ readonly numberMode?: MorphNumberMode;
55
68
  readonly fontSize?: number;
56
69
  readonly color?: string;
57
70
  readonly align?: LaminarAlign;
58
71
  readonly className?: string;
59
- /** optional inline element reconciled and animated as the leading token */
72
+ /** optional inline element reconciled and animated before the rendered value */
60
73
  readonly leading?: ReactNode | LaminarLeadingMap;
61
74
  /** identity for swapping one leading element for another */
62
75
  readonly leadingKey?: string | number;
63
76
  /** spacing between the leading element and the first text glyph */
64
77
  readonly leadingGap?: number;
78
+ /** opt-in top and bottom fades for slot reels */
79
+ readonly shadow?: LaminarShadow;
65
80
  readonly style?: StyleProp<TextStyle>;
66
81
  readonly containerStyle?: StyleProp<ViewStyle>;
67
82
  readonly fontStyle?: StyleProp<TextStyle>;
@@ -0,0 +1,45 @@
1
+ import React, { type ReactNode } from "react";
2
+ import Animated, {
3
+ type EntryExitAnimationFunction,
4
+ } from "react-native-reanimated";
5
+
6
+ type LeadingElementProps = {
7
+ readonly leading?: ReactNode;
8
+ readonly leadingKey?: string | number;
9
+ readonly leadingGap: number;
10
+ readonly enterTransition?: EntryExitAnimationFunction;
11
+ readonly elementEnterTransition?: EntryExitAnimationFunction;
12
+ readonly exitTransition?: EntryExitAnimationFunction;
13
+ readonly elementExitTransition?: EntryExitAnimationFunction;
14
+ };
15
+
16
+ // render an inline element independently so it can enter, exit, and swap beside any run
17
+ export const LeadingElement = React.memo(function LeadingElement({
18
+ leading,
19
+ leadingKey,
20
+ leadingGap,
21
+ enterTransition,
22
+ elementEnterTransition,
23
+ exitTransition,
24
+ elementExitTransition,
25
+ }: LeadingElementProps) {
26
+ if (!leading) {
27
+ return null;
28
+ }
29
+
30
+ return (
31
+ <Animated.View
32
+ key={`leading:${String(leadingKey ?? "default")}`}
33
+ entering={elementEnterTransition ?? enterTransition}
34
+ exiting={elementExitTransition ?? exitTransition}
35
+ style={[
36
+ { alignItems: "center", justifyContent: "center" },
37
+ leadingGap > 0 ? { marginRight: leadingGap } : undefined,
38
+ ]}
39
+ >
40
+ {leading}
41
+ </Animated.View>
42
+ );
43
+ });
44
+
45
+ LeadingElement.displayName = "LeadingElement";
@@ -1,7 +1,8 @@
1
- import React, { useMemo } from "react";
1
+ import React, { useMemo, useRef } from "react";
2
2
  import type { LayoutChangeEvent, StyleProp, ViewStyle } from "react-native";
3
3
  import { View } from "react-native";
4
4
  import Animated from "react-native-reanimated";
5
+ import type { ComplexAnimationBuilder } from "react-native-reanimated";
5
6
  import type { LaminarAlign } from "../types";
6
7
 
7
8
  const shellStyle = {
@@ -52,6 +53,8 @@ type MorphViewportProps = {
52
53
  readonly align: LaminarAlign;
53
54
  readonly containerStyle?: StyleProp<ViewStyle>;
54
55
  readonly animatedWidthStyle?: React.ComponentProps<typeof Animated.View>["style"];
56
+ /** optional layout-driven auto-size path for content that needs an aligned anchor */
57
+ readonly autoSizeLayoutTransition?: ComplexAnimationBuilder;
55
58
  readonly onVisibleLayout?: (event: LayoutChangeEvent) => void;
56
59
  readonly measurement?: React.ReactNode;
57
60
  readonly children: React.ReactNode;
@@ -65,10 +68,16 @@ export const MorphViewport = React.memo(
65
68
  align,
66
69
  containerStyle,
67
70
  animatedWidthStyle,
71
+ autoSizeLayoutTransition,
68
72
  onVisibleLayout,
69
73
  measurement,
70
74
  children,
71
75
  }: MorphViewportProps) => {
76
+ const hasRenderedRef = useRef(false);
77
+ const shouldAnimateAutoSizeLayout =
78
+ autoSizeLayoutTransition !== undefined && hasRenderedRef.current;
79
+ hasRenderedRef.current = true;
80
+
72
81
  // compute alignment once so the animated shell and its child share one layout contract
73
82
  const resolvedViewportStyle = useMemo(
74
83
  () => [
@@ -80,6 +89,35 @@ export const MorphViewport = React.memo(
80
89
  );
81
90
 
82
91
  // keep measurement outside the visible viewport so it cannot affect accessibility or layout
92
+ if (autoSizeLayoutTransition) {
93
+ // Layout animation owns both width and the shell's aligned x-position.
94
+ // This is important for center/right auto-size: animating only a child
95
+ // width leaves the parent anchor at its old position.
96
+ return (
97
+ <Animated.View
98
+ layout={
99
+ shouldAnimateAutoSizeLayout
100
+ ? autoSizeLayoutTransition
101
+ : undefined
102
+ }
103
+ style={[shellStyle, shellAlignStyles[align], containerStyle]}
104
+ >
105
+ <View
106
+ accessibilityElementsHidden
107
+ collapsable={false}
108
+ importantForAccessibility="no-hide-descendants"
109
+ pointerEvents="none"
110
+ style={measuredContentStyle}
111
+ >
112
+ {measurement}
113
+ </View>
114
+ <Animated.View onLayout={onVisibleLayout} style={resolvedViewportStyle}>
115
+ {children}
116
+ </Animated.View>
117
+ </Animated.View>
118
+ );
119
+ }
120
+
83
121
  return (
84
122
  <View style={[shellStyle, shellAlignStyles[align], containerStyle]}>
85
123
  {autoSize ? (