@number10/jsx-core 5.0.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 (340) hide show
  1. package/LICENSE +667 -0
  2. package/README.md +38 -0
  3. package/dist/animation/animated-signal.cjs +19 -0
  4. package/dist/animation/animated-signal.cjs.map +1 -0
  5. package/dist/animation/animated-signal.d.ts +35 -0
  6. package/dist/animation/animated-signal.d.ts.map +1 -0
  7. package/dist/animation/animated-signal.js +19 -0
  8. package/dist/animation/animated-signal.js.map +1 -0
  9. package/dist/animation/create-spring.cjs +80 -0
  10. package/dist/animation/create-spring.cjs.map +1 -0
  11. package/dist/animation/create-spring.d.ts +56 -0
  12. package/dist/animation/create-spring.d.ts.map +1 -0
  13. package/dist/animation/create-spring.js +80 -0
  14. package/dist/animation/create-spring.js.map +1 -0
  15. package/dist/animation/easing.cjs +76 -0
  16. package/dist/animation/easing.cjs.map +1 -0
  17. package/dist/animation/easing.d.ts +45 -0
  18. package/dist/animation/easing.d.ts.map +1 -0
  19. package/dist/animation/easing.js +76 -0
  20. package/dist/animation/easing.js.map +1 -0
  21. package/dist/animation/effect-definitions.cjs +441 -0
  22. package/dist/animation/effect-definitions.cjs.map +1 -0
  23. package/dist/animation/effect-definitions.d.ts +60 -0
  24. package/dist/animation/effect-definitions.d.ts.map +1 -0
  25. package/dist/animation/effect-definitions.js +441 -0
  26. package/dist/animation/effect-definitions.js.map +1 -0
  27. package/dist/animation/effect-engine.cjs +189 -0
  28. package/dist/animation/effect-engine.cjs.map +1 -0
  29. package/dist/animation/effect-engine.d.ts +70 -0
  30. package/dist/animation/effect-engine.d.ts.map +1 -0
  31. package/dist/animation/effect-engine.js +189 -0
  32. package/dist/animation/effect-engine.js.map +1 -0
  33. package/dist/animation/index.d.ts +24 -0
  34. package/dist/animation/index.d.ts.map +1 -0
  35. package/dist/animation/spring-physics.cjs +94 -0
  36. package/dist/animation/spring-physics.cjs.map +1 -0
  37. package/dist/animation/spring-physics.d.ts +145 -0
  38. package/dist/animation/spring-physics.d.ts.map +1 -0
  39. package/dist/animation/spring-physics.js +94 -0
  40. package/dist/animation/spring-physics.js.map +1 -0
  41. package/dist/animation/tween.cjs +67 -0
  42. package/dist/animation/tween.cjs.map +1 -0
  43. package/dist/animation/tween.d.ts +60 -0
  44. package/dist/animation/tween.d.ts.map +1 -0
  45. package/dist/animation/tween.js +67 -0
  46. package/dist/animation/tween.js.map +1 -0
  47. package/dist/animation/types.d.ts +31 -0
  48. package/dist/animation/types.d.ts.map +1 -0
  49. package/dist/animation/useSpring.cjs +152 -0
  50. package/dist/animation/useSpring.cjs.map +1 -0
  51. package/dist/animation/useSpring.d.ts +55 -0
  52. package/dist/animation/useSpring.d.ts.map +1 -0
  53. package/dist/animation/useSpring.js +149 -0
  54. package/dist/animation/useSpring.js.map +1 -0
  55. package/dist/animation/useTween.cjs +208 -0
  56. package/dist/animation/useTween.cjs.map +1 -0
  57. package/dist/animation/useTween.d.ts +116 -0
  58. package/dist/animation/useTween.d.ts.map +1 -0
  59. package/dist/animation/useTween.js +208 -0
  60. package/dist/animation/useTween.js.map +1 -0
  61. package/dist/border-geometry.cjs +447 -0
  62. package/dist/border-geometry.cjs.map +1 -0
  63. package/dist/border-geometry.d.ts +114 -0
  64. package/dist/border-geometry.d.ts.map +1 -0
  65. package/dist/border-geometry.js +447 -0
  66. package/dist/border-geometry.js.map +1 -0
  67. package/dist/colors/color-presets.cjs +236 -0
  68. package/dist/colors/color-presets.cjs.map +1 -0
  69. package/dist/colors/color-presets.d.ts +89 -0
  70. package/dist/colors/color-presets.d.ts.map +1 -0
  71. package/dist/colors/color-presets.js +236 -0
  72. package/dist/colors/color-presets.js.map +1 -0
  73. package/dist/colors/color-theme-helpers.cjs +32 -0
  74. package/dist/colors/color-theme-helpers.cjs.map +1 -0
  75. package/dist/colors/color-theme-helpers.d.ts +65 -0
  76. package/dist/colors/color-theme-helpers.d.ts.map +1 -0
  77. package/dist/colors/color-theme-helpers.js +32 -0
  78. package/dist/colors/color-theme-helpers.js.map +1 -0
  79. package/dist/colors/color-types.cjs +38 -0
  80. package/dist/colors/color-types.cjs.map +1 -0
  81. package/dist/colors/color-types.d.ts +112 -0
  82. package/dist/colors/color-types.d.ts.map +1 -0
  83. package/dist/colors/color-types.js +38 -0
  84. package/dist/colors/color-types.js.map +1 -0
  85. package/dist/colors/color-utils.cjs +197 -0
  86. package/dist/colors/color-utils.cjs.map +1 -0
  87. package/dist/colors/color-utils.d.ts +216 -0
  88. package/dist/colors/color-utils.d.ts.map +1 -0
  89. package/dist/colors/color-utils.js +197 -0
  90. package/dist/colors/color-utils.js.map +1 -0
  91. package/dist/colors/contrast-contracts.cjs +265 -0
  92. package/dist/colors/contrast-contracts.cjs.map +1 -0
  93. package/dist/colors/contrast-contracts.d.ts +289 -0
  94. package/dist/colors/contrast-contracts.d.ts.map +1 -0
  95. package/dist/colors/contrast-contracts.js +265 -0
  96. package/dist/colors/contrast-contracts.js.map +1 -0
  97. package/dist/colors/index.cjs +55 -0
  98. package/dist/colors/index.cjs.map +1 -0
  99. package/dist/colors/index.d.ts +16 -0
  100. package/dist/colors/index.d.ts.map +1 -0
  101. package/dist/colors/index.js +55 -0
  102. package/dist/colors/index.js.map +1 -0
  103. package/dist/core-props.cjs +66 -0
  104. package/dist/core-props.cjs.map +1 -0
  105. package/dist/core-props.d.ts +395 -0
  106. package/dist/core-props.d.ts.map +1 -0
  107. package/dist/core-props.js +66 -0
  108. package/dist/core-props.js.map +1 -0
  109. package/dist/design-tokens/design-token-presets.cjs +91 -0
  110. package/dist/design-tokens/design-token-presets.cjs.map +1 -0
  111. package/dist/design-tokens/design-token-presets.d.ts +34 -0
  112. package/dist/design-tokens/design-token-presets.d.ts.map +1 -0
  113. package/dist/design-tokens/design-token-presets.js +91 -0
  114. package/dist/design-tokens/design-token-presets.js.map +1 -0
  115. package/dist/design-tokens/design-token-types.d.ts +97 -0
  116. package/dist/design-tokens/design-token-types.d.ts.map +1 -0
  117. package/dist/design-tokens/index.cjs +10 -0
  118. package/dist/design-tokens/index.cjs.map +1 -0
  119. package/dist/design-tokens/index.d.ts +9 -0
  120. package/dist/design-tokens/index.d.ts.map +1 -0
  121. package/dist/design-tokens/index.js +10 -0
  122. package/dist/design-tokens/index.js.map +1 -0
  123. package/dist/dev-config.cjs +296 -0
  124. package/dist/dev-config.cjs.map +1 -0
  125. package/dist/dev-config.d.ts +238 -0
  126. package/dist/dev-config.d.ts.map +1 -0
  127. package/dist/dev-config.js +296 -0
  128. package/dist/dev-config.js.map +1 -0
  129. package/dist/effects.cjs +69 -0
  130. package/dist/effects.cjs.map +1 -0
  131. package/dist/effects.d.ts +152 -0
  132. package/dist/effects.d.ts.map +1 -0
  133. package/dist/effects.js +69 -0
  134. package/dist/effects.js.map +1 -0
  135. package/dist/global-registry.cjs +25 -0
  136. package/dist/global-registry.cjs.map +1 -0
  137. package/dist/global-registry.d.ts +19 -0
  138. package/dist/global-registry.d.ts.map +1 -0
  139. package/dist/global-registry.js +25 -0
  140. package/dist/global-registry.js.map +1 -0
  141. package/dist/hooks.cjs +378 -0
  142. package/dist/hooks.cjs.map +1 -0
  143. package/dist/hooks.d.ts +322 -0
  144. package/dist/hooks.d.ts.map +1 -0
  145. package/dist/hooks.js +378 -0
  146. package/dist/hooks.js.map +1 -0
  147. package/dist/host-adapter.d.ts +414 -0
  148. package/dist/host-adapter.d.ts.map +1 -0
  149. package/dist/index.cjs +229 -0
  150. package/dist/index.cjs.map +1 -0
  151. package/dist/index.d.ts +32 -0
  152. package/dist/index.d.ts.map +1 -0
  153. package/dist/index.js +226 -0
  154. package/dist/index.js.map +1 -0
  155. package/dist/jsx-dev-runtime.cjs +10 -0
  156. package/dist/jsx-dev-runtime.cjs.map +1 -0
  157. package/dist/jsx-dev-runtime.d.ts +5 -0
  158. package/dist/jsx-dev-runtime.d.ts.map +1 -0
  159. package/dist/jsx-dev-runtime.js +11 -0
  160. package/dist/jsx-dev-runtime.js.map +1 -0
  161. package/dist/jsx-runtime.cjs +44 -0
  162. package/dist/jsx-runtime.cjs.map +1 -0
  163. package/dist/jsx-runtime.d.ts +24 -0
  164. package/dist/jsx-runtime.d.ts.map +1 -0
  165. package/dist/jsx-runtime.js +44 -0
  166. package/dist/jsx-runtime.js.map +1 -0
  167. package/dist/jsx-types.d.ts +25 -0
  168. package/dist/jsx-types.d.ts.map +1 -0
  169. package/dist/layout/appliers/container-applier.cjs +20 -0
  170. package/dist/layout/appliers/container-applier.cjs.map +1 -0
  171. package/dist/layout/appliers/container-applier.d.ts +9 -0
  172. package/dist/layout/appliers/container-applier.d.ts.map +1 -0
  173. package/dist/layout/appliers/container-applier.js +20 -0
  174. package/dist/layout/appliers/container-applier.js.map +1 -0
  175. package/dist/layout/appliers/position-applier.cjs +31 -0
  176. package/dist/layout/appliers/position-applier.cjs.map +1 -0
  177. package/dist/layout/appliers/position-applier.d.ts +8 -0
  178. package/dist/layout/appliers/position-applier.d.ts.map +1 -0
  179. package/dist/layout/appliers/position-applier.js +31 -0
  180. package/dist/layout/appliers/position-applier.js.map +1 -0
  181. package/dist/layout/layout-engine-test-utils.d.ts +26 -0
  182. package/dist/layout/layout-engine-test-utils.d.ts.map +1 -0
  183. package/dist/layout/layout-engine.cjs +628 -0
  184. package/dist/layout/layout-engine.cjs.map +1 -0
  185. package/dist/layout/layout-engine.d.ts +140 -0
  186. package/dist/layout/layout-engine.d.ts.map +1 -0
  187. package/dist/layout/layout-engine.js +628 -0
  188. package/dist/layout/layout-engine.js.map +1 -0
  189. package/dist/layout/strategies/base-strategy.cjs +31 -0
  190. package/dist/layout/strategies/base-strategy.cjs.map +1 -0
  191. package/dist/layout/strategies/base-strategy.d.ts +59 -0
  192. package/dist/layout/strategies/base-strategy.d.ts.map +1 -0
  193. package/dist/layout/strategies/base-strategy.js +31 -0
  194. package/dist/layout/strategies/base-strategy.js.map +1 -0
  195. package/dist/layout/strategies/column-layout.cjs +145 -0
  196. package/dist/layout/strategies/column-layout.cjs.map +1 -0
  197. package/dist/layout/strategies/column-layout.d.ts +38 -0
  198. package/dist/layout/strategies/column-layout.d.ts.map +1 -0
  199. package/dist/layout/strategies/column-layout.js +145 -0
  200. package/dist/layout/strategies/column-layout.js.map +1 -0
  201. package/dist/layout/strategies/row-layout.cjs +145 -0
  202. package/dist/layout/strategies/row-layout.cjs.map +1 -0
  203. package/dist/layout/strategies/row-layout.d.ts +38 -0
  204. package/dist/layout/strategies/row-layout.d.ts.map +1 -0
  205. package/dist/layout/strategies/row-layout.js +145 -0
  206. package/dist/layout/strategies/row-layout.js.map +1 -0
  207. package/dist/layout/strategies/stack-layout.cjs +94 -0
  208. package/dist/layout/strategies/stack-layout.cjs.map +1 -0
  209. package/dist/layout/strategies/stack-layout.d.ts +30 -0
  210. package/dist/layout/strategies/stack-layout.d.ts.map +1 -0
  211. package/dist/layout/strategies/stack-layout.js +94 -0
  212. package/dist/layout/strategies/stack-layout.js.map +1 -0
  213. package/dist/layout/types.d.ts +134 -0
  214. package/dist/layout/types.d.ts.map +1 -0
  215. package/dist/layout/utils/child-utils.cjs +129 -0
  216. package/dist/layout/utils/child-utils.cjs.map +1 -0
  217. package/dist/layout/utils/child-utils.d.ts +54 -0
  218. package/dist/layout/utils/child-utils.d.ts.map +1 -0
  219. package/dist/layout/utils/child-utils.js +129 -0
  220. package/dist/layout/utils/child-utils.js.map +1 -0
  221. package/dist/layout/utils/dimension-calculator.cjs +109 -0
  222. package/dist/layout/utils/dimension-calculator.cjs.map +1 -0
  223. package/dist/layout/utils/dimension-calculator.d.ts +59 -0
  224. package/dist/layout/utils/dimension-calculator.d.ts.map +1 -0
  225. package/dist/layout/utils/dimension-calculator.js +109 -0
  226. package/dist/layout/utils/dimension-calculator.js.map +1 -0
  227. package/dist/layout/utils/flex-distributor.cjs +173 -0
  228. package/dist/layout/utils/flex-distributor.cjs.map +1 -0
  229. package/dist/layout/utils/flex-distributor.d.ts +18 -0
  230. package/dist/layout/utils/flex-distributor.d.ts.map +1 -0
  231. package/dist/layout/utils/flex-distributor.js +173 -0
  232. package/dist/layout/utils/flex-distributor.js.map +1 -0
  233. package/dist/layout/utils/size-resolver.cjs +280 -0
  234. package/dist/layout/utils/size-resolver.cjs.map +1 -0
  235. package/dist/layout/utils/size-resolver.d.ts +84 -0
  236. package/dist/layout/utils/size-resolver.d.ts.map +1 -0
  237. package/dist/layout/utils/size-resolver.js +280 -0
  238. package/dist/layout/utils/size-resolver.js.map +1 -0
  239. package/dist/layout/utils/spacing-calculator.cjs +50 -0
  240. package/dist/layout/utils/spacing-calculator.cjs.map +1 -0
  241. package/dist/layout/utils/spacing-calculator.d.ts +27 -0
  242. package/dist/layout/utils/spacing-calculator.d.ts.map +1 -0
  243. package/dist/layout/utils/spacing-calculator.js +50 -0
  244. package/dist/layout/utils/spacing-calculator.js.map +1 -0
  245. package/dist/memo.cjs +18 -0
  246. package/dist/memo.cjs.map +1 -0
  247. package/dist/memo.d.ts +29 -0
  248. package/dist/memo.d.ts.map +1 -0
  249. package/dist/memo.js +18 -0
  250. package/dist/memo.js.map +1 -0
  251. package/dist/nineslice-contract.cjs +60 -0
  252. package/dist/nineslice-contract.cjs.map +1 -0
  253. package/dist/nineslice-contract.d.ts +107 -0
  254. package/dist/nineslice-contract.d.ts.map +1 -0
  255. package/dist/nineslice-contract.js +60 -0
  256. package/dist/nineslice-contract.js.map +1 -0
  257. package/dist/placeholder.cjs +12 -0
  258. package/dist/placeholder.cjs.map +1 -0
  259. package/dist/placeholder.d.ts +44 -0
  260. package/dist/placeholder.d.ts.map +1 -0
  261. package/dist/placeholder.js +12 -0
  262. package/dist/placeholder.js.map +1 -0
  263. package/dist/portal.cjs +89 -0
  264. package/dist/portal.cjs.map +1 -0
  265. package/dist/portal.d.ts +62 -0
  266. package/dist/portal.d.ts.map +1 -0
  267. package/dist/portal.js +89 -0
  268. package/dist/portal.js.map +1 -0
  269. package/dist/primitive-contracts.cjs +12 -0
  270. package/dist/primitive-contracts.cjs.map +1 -0
  271. package/dist/primitive-contracts.d.ts +186 -0
  272. package/dist/primitive-contracts.d.ts.map +1 -0
  273. package/dist/primitive-contracts.js +12 -0
  274. package/dist/primitive-contracts.js.map +1 -0
  275. package/dist/theme.cjs +332 -0
  276. package/dist/theme.cjs.map +1 -0
  277. package/dist/theme.d.ts +204 -0
  278. package/dist/theme.d.ts.map +1 -0
  279. package/dist/theme.js +332 -0
  280. package/dist/theme.js.map +1 -0
  281. package/dist/types.d.ts +86 -0
  282. package/dist/types.d.ts.map +1 -0
  283. package/dist/vdom/create-runtime.cjs +79 -0
  284. package/dist/vdom/create-runtime.cjs.map +1 -0
  285. package/dist/vdom/create-runtime.d.ts +7 -0
  286. package/dist/vdom/create-runtime.d.ts.map +1 -0
  287. package/dist/vdom/create-runtime.js +79 -0
  288. package/dist/vdom/create-runtime.js.map +1 -0
  289. package/dist/vdom/index.cjs +8 -0
  290. package/dist/vdom/index.cjs.map +1 -0
  291. package/dist/vdom/index.d.ts +4 -0
  292. package/dist/vdom/index.d.ts.map +1 -0
  293. package/dist/vdom/index.js +8 -0
  294. package/dist/vdom/index.js.map +1 -0
  295. package/dist/vdom/mount.cjs +161 -0
  296. package/dist/vdom/mount.cjs.map +1 -0
  297. package/dist/vdom/mount.d.ts +6 -0
  298. package/dist/vdom/mount.d.ts.map +1 -0
  299. package/dist/vdom/mount.js +161 -0
  300. package/dist/vdom/mount.js.map +1 -0
  301. package/dist/vdom/patch.cjs +163 -0
  302. package/dist/vdom/patch.cjs.map +1 -0
  303. package/dist/vdom/patch.d.ts +3 -0
  304. package/dist/vdom/patch.d.ts.map +1 -0
  305. package/dist/vdom/patch.js +163 -0
  306. package/dist/vdom/patch.js.map +1 -0
  307. package/dist/vdom/registry.cjs +85 -0
  308. package/dist/vdom/registry.cjs.map +1 -0
  309. package/dist/vdom/registry.d.ts +28 -0
  310. package/dist/vdom/registry.d.ts.map +1 -0
  311. package/dist/vdom/registry.js +85 -0
  312. package/dist/vdom/registry.js.map +1 -0
  313. package/dist/vdom/runtime-helpers.cjs +23 -0
  314. package/dist/vdom/runtime-helpers.cjs.map +1 -0
  315. package/dist/vdom/runtime-helpers.d.ts +8 -0
  316. package/dist/vdom/runtime-helpers.d.ts.map +1 -0
  317. package/dist/vdom/runtime-helpers.js +23 -0
  318. package/dist/vdom/runtime-helpers.js.map +1 -0
  319. package/dist/vdom/surface.cjs +107 -0
  320. package/dist/vdom/surface.cjs.map +1 -0
  321. package/dist/vdom/surface.d.ts +9 -0
  322. package/dist/vdom/surface.d.ts.map +1 -0
  323. package/dist/vdom/surface.js +107 -0
  324. package/dist/vdom/surface.js.map +1 -0
  325. package/dist/vdom/types.d.ts +104 -0
  326. package/dist/vdom/types.d.ts.map +1 -0
  327. package/dist/vdom/utils.cjs +146 -0
  328. package/dist/vdom/utils.cjs.map +1 -0
  329. package/dist/vdom/utils.d.ts +24 -0
  330. package/dist/vdom/utils.d.ts.map +1 -0
  331. package/dist/vdom/utils.js +146 -0
  332. package/dist/vdom/utils.js.map +1 -0
  333. package/dist/vdom.d.ts +4 -0
  334. package/dist/vdom.d.ts.map +1 -0
  335. package/dist/vector-draw-context.d.ts +63 -0
  336. package/dist/vector-draw-context.d.ts.map +1 -0
  337. package/package.json +161 -0
  338. package/vdom/index.cjs +1 -0
  339. package/vdom/index.d.ts +1 -0
  340. package/vdom/index.js +1 -0
@@ -0,0 +1,94 @@
1
+ const DEFAULT_SPRING_CONFIG = {
2
+ tension: 170,
3
+ friction: 26,
4
+ mass: 1,
5
+ restVelocity: 0.01,
6
+ restDelta: 0.01,
7
+ clamp: false
8
+ };
9
+ const SPRING_PRESETS = {
10
+ /** Gentle, but still responsive animation */
11
+ gentle: { tension: 350, friction: 24, restVelocity: 0.5, restDelta: 0.1 },
12
+ /** Moderate, balanced animation (default) */
13
+ default: { tension: 600, friction: 40, restVelocity: 0.5, restDelta: 0.1 },
14
+ /** Bouncy, playful animation tuned for quick UI feedback */
15
+ wobbly: { tension: 900, friction: 35, restVelocity: 0.5, restDelta: 0.1 },
16
+ /** Very fast, snappy response for press/pulse-style interactions */
17
+ stiff: { tension: 1800, friction: 60, restVelocity: 0.5, restDelta: 0.1 },
18
+ /** Slow, deliberate animation */
19
+ slow: { tension: 350, friction: 48, restVelocity: 0.5, restDelta: 0.1 },
20
+ /** Nearly instant transition */
21
+ instant: { tension: 2500, friction: 90, restVelocity: 1, restDelta: 0.02, clamp: true }
22
+ };
23
+ class SpringPhysics {
24
+ config;
25
+ /**
26
+ * Creates a new spring physics engine
27
+ * @param config - Spring configuration
28
+ */
29
+ constructor(config = {}) {
30
+ this.config = { ...DEFAULT_SPRING_CONFIG, ...config };
31
+ }
32
+ /**
33
+ * Updates spring configuration
34
+ * @param config - Partial config to merge with current
35
+ */
36
+ updateConfig(config) {
37
+ this.config = { ...this.config, ...config };
38
+ }
39
+ /**
40
+ * Performs one physics step
41
+ * Uses semi-implicit Euler integration for stability
42
+ * @param state - Current spring state
43
+ * @param target - Target value
44
+ * @param deltaTime - Time delta in seconds
45
+ * @returns New spring state
46
+ */
47
+ step(state, target, deltaTime) {
48
+ const { tension, friction, mass, clamp } = this.config;
49
+ const springForce = -tension * (state.value - target);
50
+ const dampingForce = -friction * state.velocity;
51
+ const acceleration = (springForce + dampingForce) / mass;
52
+ const newVelocity = state.velocity + acceleration * deltaTime;
53
+ let newValue = state.value + newVelocity * deltaTime;
54
+ if (clamp) {
55
+ const min = Math.min(state.value, target);
56
+ const max = Math.max(state.value, target);
57
+ newValue = Math.max(min, Math.min(max, newValue));
58
+ }
59
+ return {
60
+ value: newValue,
61
+ velocity: newVelocity
62
+ };
63
+ }
64
+ /**
65
+ * Checks if spring has reached rest state
66
+ * @param state - Current spring state
67
+ * @param target - Target value
68
+ * @returns True if spring is at rest
69
+ */
70
+ isAtRest(state, target) {
71
+ const { restVelocity, restDelta } = this.config;
72
+ const isVelocitySmall = Math.abs(state.velocity) <= restVelocity;
73
+ const isDisplacementSmall = Math.abs(state.value - target) <= restDelta;
74
+ return isVelocitySmall && isDisplacementSmall;
75
+ }
76
+ /**
77
+ * Snaps spring to target if at rest
78
+ * @param state - Current spring state
79
+ * @param target - Target value
80
+ * @returns Adjusted state (snapped if at rest)
81
+ */
82
+ snapIfAtRest(state, target) {
83
+ if (this.isAtRest(state, target)) {
84
+ return { value: target, velocity: 0 };
85
+ }
86
+ return state;
87
+ }
88
+ }
89
+ export {
90
+ DEFAULT_SPRING_CONFIG,
91
+ SPRING_PRESETS,
92
+ SpringPhysics
93
+ };
94
+ //# sourceMappingURL=spring-physics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spring-physics.js","sources":["../../src/animation/spring-physics.ts"],"sourcesContent":["/**\n * Spring physics engine for smooth, natural animations\n * Based on spring-damper system with configurable tension, friction, and mass\n *\n * @migration(audit:surface-access) Phase 7 — moved to @number10/jsx-core ✅\n */\n\n/**\n * Configuration for spring physics behavior\n */\nexport interface SpringConfig {\n /** Spring stiffness (higher = faster, snappier). Default: 170 */\n tension?: number\n /** Damping force (higher = less oscillation). Default: 26 */\n friction?: number\n /** Mass of the animated object (higher = slower). Default: 1 */\n mass?: number\n /** Velocity threshold below which animation stops. Default: 0.01 */\n restVelocity?: number\n /** Distance threshold below which animation stops. Default: 0.01 */\n restDelta?: number\n /** Clamp values to prevent overshoot. Default: false */\n clamp?: boolean\n /**\n * Called once when the spring starts moving from rest.\n * Fires BEFORE the first physics step.\n */\n onStart?: (() => void) | undefined\n /**\n * Called on every frame with the current spring value.\n * Useful for syncing non-signal state.\n */\n onUpdate?: ((value: number) => void) | undefined\n}\n\n/**\n * Spring physics state\n */\nexport interface SpringState {\n /** Current value */\n value: number\n /** Current velocity */\n velocity: number\n}\n\n/**\n * Default spring configuration (similar to react-spring)\n */\nexport const DEFAULT_SPRING_CONFIG: Required<Omit<SpringConfig, 'onStart' | 'onUpdate'>> = {\n tension: 170,\n friction: 26,\n mass: 1,\n restVelocity: 0.01,\n restDelta: 0.01,\n clamp: false,\n}\n\n/**\n * Preset spring configurations for common use cases\n * Note: clamp prevents overshoot but can make animations feel less natural\n * For UI animations, consider enabling clamp to prevent visual artifacts\n */\nexport const SPRING_PRESETS = {\n /** Gentle, but still responsive animation */\n gentle: { tension: 350, friction: 24, restVelocity: 0.5, restDelta: 0.1 },\n /** Moderate, balanced animation (default) */\n default: { tension: 600, friction: 40, restVelocity: 0.5, restDelta: 0.1 },\n /** Bouncy, playful animation tuned for quick UI feedback */\n wobbly: { tension: 900, friction: 35, restVelocity: 0.5, restDelta: 0.1 },\n /** Very fast, snappy response for press/pulse-style interactions */\n stiff: { tension: 1800, friction: 60, restVelocity: 0.5, restDelta: 0.1 },\n /** Slow, deliberate animation */\n slow: { tension: 350, friction: 48, restVelocity: 0.5, restDelta: 0.1 },\n /** Nearly instant transition */\n instant: { tension: 2500, friction: 90, restVelocity: 1, restDelta: 0.02, clamp: true },\n} as const\n\n/**\n * Type for animation preset keys\n */\nexport type AnimationPreset = keyof typeof SPRING_PRESETS\n/**\n * Type for animation configuration (preset name or custom config)\n */\nexport type AnimationConfig = AnimationPreset | SpringConfig\n\n/**\n * Spring physics engine\n * Implements spring-damper physics for smooth, natural animations\n */\nexport class SpringPhysics {\n private config: Required<Omit<SpringConfig, 'onStart' | 'onUpdate'>> &\n Pick<SpringConfig, 'onStart' | 'onUpdate'>\n\n /**\n * Creates a new spring physics engine\n * @param config - Spring configuration\n */\n constructor(config: SpringConfig = {}) {\n this.config = { ...DEFAULT_SPRING_CONFIG, ...config } as typeof this.config\n }\n\n /**\n * Updates spring configuration\n * @param config - Partial config to merge with current\n */\n updateConfig(config: Partial<SpringConfig>): void {\n this.config = { ...this.config, ...config } as typeof this.config\n }\n\n /**\n * Performs one physics step\n * Uses semi-implicit Euler integration for stability\n * @param state - Current spring state\n * @param target - Target value\n * @param deltaTime - Time delta in seconds\n * @returns New spring state\n */\n step(state: SpringState, target: number, deltaTime: number): SpringState {\n const { tension, friction, mass, clamp } = this.config\n\n // Calculate spring force (Hooke's law)\n const springForce = -tension * (state.value - target)\n\n // Calculate damping force\n const dampingForce = -friction * state.velocity\n\n // Calculate acceleration (F = ma → a = F/m)\n const acceleration = (springForce + dampingForce) / mass\n\n // Update velocity (semi-implicit Euler)\n const newVelocity = state.velocity + acceleration * deltaTime\n\n // Update position\n let newValue = state.value + newVelocity * deltaTime\n\n // Clamp if enabled\n if (clamp) {\n const min = Math.min(state.value, target)\n const max = Math.max(state.value, target)\n newValue = Math.max(min, Math.min(max, newValue))\n }\n\n return {\n value: newValue,\n velocity: newVelocity,\n }\n }\n\n /**\n * Checks if spring has reached rest state\n * @param state - Current spring state\n * @param target - Target value\n * @returns True if spring is at rest\n */\n isAtRest(state: SpringState, target: number): boolean {\n const { restVelocity, restDelta } = this.config\n const isVelocitySmall = Math.abs(state.velocity) <= restVelocity\n const isDisplacementSmall = Math.abs(state.value - target) <= restDelta\n return isVelocitySmall && isDisplacementSmall\n }\n\n /**\n * Snaps spring to target if at rest\n * @param state - Current spring state\n * @param target - Target value\n * @returns Adjusted state (snapped if at rest)\n */\n snapIfAtRest(state: SpringState, target: number): SpringState {\n if (this.isAtRest(state, target)) {\n return { value: target, velocity: 0 }\n }\n return state\n }\n}\n"],"names":[],"mappings":"AAgDO,MAAM,wBAA8E;AAAA,EACzF,SAAS;AAAA,EACT,UAAU;AAAA,EACV,MAAM;AAAA,EACN,cAAc;AAAA,EACd,WAAW;AAAA,EACX,OAAO;AACT;AAOO,MAAM,iBAAiB;AAAA;AAAA,EAE5B,QAAQ,EAAE,SAAS,KAAK,UAAU,IAAI,cAAc,KAAK,WAAW,IAAA;AAAA;AAAA,EAEpE,SAAS,EAAE,SAAS,KAAK,UAAU,IAAI,cAAc,KAAK,WAAW,IAAA;AAAA;AAAA,EAErE,QAAQ,EAAE,SAAS,KAAK,UAAU,IAAI,cAAc,KAAK,WAAW,IAAA;AAAA;AAAA,EAEpE,OAAO,EAAE,SAAS,MAAM,UAAU,IAAI,cAAc,KAAK,WAAW,IAAA;AAAA;AAAA,EAEpE,MAAM,EAAE,SAAS,KAAK,UAAU,IAAI,cAAc,KAAK,WAAW,IAAA;AAAA;AAAA,EAElE,SAAS,EAAE,SAAS,MAAM,UAAU,IAAI,cAAc,GAAG,WAAW,MAAM,OAAO,KAAA;AACnF;AAeO,MAAM,cAAc;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA,EAOR,YAAY,SAAuB,IAAI;AACrC,SAAK,SAAS,EAAE,GAAG,uBAAuB,GAAG,OAAA;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,QAAqC;AAChD,SAAK,SAAS,EAAE,GAAG,KAAK,QAAQ,GAAG,OAAA;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,KAAK,OAAoB,QAAgB,WAAgC;AACvE,UAAM,EAAE,SAAS,UAAU,MAAM,MAAA,IAAU,KAAK;AAGhD,UAAM,cAAc,CAAC,WAAW,MAAM,QAAQ;AAG9C,UAAM,eAAe,CAAC,WAAW,MAAM;AAGvC,UAAM,gBAAgB,cAAc,gBAAgB;AAGpD,UAAM,cAAc,MAAM,WAAW,eAAe;AAGpD,QAAI,WAAW,MAAM,QAAQ,cAAc;AAG3C,QAAI,OAAO;AACT,YAAM,MAAM,KAAK,IAAI,MAAM,OAAO,MAAM;AACxC,YAAM,MAAM,KAAK,IAAI,MAAM,OAAO,MAAM;AACxC,iBAAW,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,QAAQ,CAAC;AAAA,IAClD;AAEA,WAAO;AAAA,MACL,OAAO;AAAA,MACP,UAAU;AAAA,IAAA;AAAA,EAEd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,SAAS,OAAoB,QAAyB;AACpD,UAAM,EAAE,cAAc,UAAA,IAAc,KAAK;AACzC,UAAM,kBAAkB,KAAK,IAAI,MAAM,QAAQ,KAAK;AACpD,UAAM,sBAAsB,KAAK,IAAI,MAAM,QAAQ,MAAM,KAAK;AAC9D,WAAO,mBAAmB;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,aAAa,OAAoB,QAA6B;AAC5D,QAAI,KAAK,SAAS,OAAO,MAAM,GAAG;AAChC,aAAO,EAAE,OAAO,QAAQ,UAAU,EAAA;AAAA,IACpC;AACA,WAAO;AAAA,EACT;AACF;"}
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const easing = require("./easing.cjs");
4
+ function createTween(initial, config, scheduler) {
5
+ const target = { ...initial };
6
+ const startValues = { ...initial };
7
+ const keys = Object.keys(config.to);
8
+ const ease = config.ease ?? easing.Easing.quadOut;
9
+ const startTime = performance.now();
10
+ let frameHandle = null;
11
+ let completed = false;
12
+ if (config.duration <= 0) {
13
+ for (const key of keys) {
14
+ const toVal = config.to[key];
15
+ if (toVal !== void 0) {
16
+ target[key] = toVal;
17
+ }
18
+ }
19
+ config.onUpdate?.(target);
20
+ config.onComplete?.();
21
+ return {
22
+ target,
23
+ stop: () => {
24
+ },
25
+ isActive: false
26
+ };
27
+ }
28
+ frameHandle = scheduler.onFrame((_dt) => {
29
+ if (completed) return;
30
+ const elapsed = performance.now() - startTime;
31
+ const rawProgress = Math.min(elapsed / config.duration, 1);
32
+ const easedProgress = ease(rawProgress);
33
+ for (const key of keys) {
34
+ const toVal = config.to[key];
35
+ if (toVal !== void 0) {
36
+ const fromVal = startValues[key];
37
+ target[key] = fromVal + (toVal - fromVal) * easedProgress;
38
+ }
39
+ }
40
+ try {
41
+ config.onUpdate?.(target);
42
+ } catch {
43
+ }
44
+ if (rawProgress >= 1) {
45
+ completed = true;
46
+ frameHandle?.cancel();
47
+ try {
48
+ config.onComplete?.();
49
+ } catch {
50
+ }
51
+ }
52
+ });
53
+ return {
54
+ target,
55
+ stop: () => {
56
+ if (completed) return;
57
+ completed = true;
58
+ frameHandle?.cancel();
59
+ },
60
+ get isActive() {
61
+ return !completed;
62
+ }
63
+ };
64
+ }
65
+ exports.Easing = easing.Easing;
66
+ exports.createTween = createTween;
67
+ //# sourceMappingURL=tween.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tween.cjs","sources":["../../src/animation/tween.ts"],"sourcesContent":["/**\n * Animation utilities — renderer-neutral tween engine.\n *\n * Provides time-based value interpolation with configurable easing.\n * Runs on HostScheduler.onFrame() — no renderer-specific tween system needed.\n *\n * This is the duration+easing counterpart to the spring-physics animation\n * (useSpring). Use createTween when you need exact duration control;\n * use useSpring for continuous physics-based motion.\n *\n * @migration(audit:surface-access) Phase 7 — new Core capability.\n * Replaces scene.tweens.add() in portable components.\n */\n\nimport type { HostCancelHandle, HostScheduler } from '../host-adapter'\nimport { Easing, type EasingFn } from './easing'\n\n// Re-export for backward compatibility — all easing functions now live in easing.ts\nexport { Easing, type EasingFn } from './easing'\n\n// ---------------------------------------------------------------------------\n// Tween types\n// ---------------------------------------------------------------------------\n\n/**\n * Configuration for a tween animation.\n * @typeParam T - Record of numeric properties to animate\n */\nexport interface TweenConfig<T extends Record<string, number>> {\n /** Target values (partial — only specified keys are animated) */\n to: Partial<T>\n /** Duration in milliseconds */\n duration: number\n /** Easing function (default: quadOut) */\n ease?: EasingFn | undefined\n /** Called every frame with the current interpolated target */\n onUpdate?: ((target: T) => void) | undefined\n /** Called when the tween completes naturally (not on stop()) */\n onComplete?: (() => void) | undefined\n}\n\n/**\n * Handle to an active tween animation.\n */\nexport interface TweenHandle<T extends Record<string, number> = Record<string, number>> {\n /** Current interpolated state (mutated each frame) */\n readonly target: Readonly<T>\n /** Stop the animation immediately. onComplete is NOT called. */\n stop(): void\n /** Whether the animation is still running */\n readonly isActive: boolean\n}\n\n// ---------------------------------------------------------------------------\n// createTween\n// ---------------------------------------------------------------------------\n\n/**\n * Start a time-based value interpolation.\n *\n * Animates numeric properties of a plain object from their current values\n * to the specified target values over a fixed duration.\n *\n * @param initial - Starting values for all animated properties\n * @param config - Animation configuration (target, duration, easing, callbacks)\n * @param scheduler - HostScheduler for frame updates (from SurfaceContext)\n * @returns TweenHandle for tracking and cancellation\n *\n * @example\n * ```typescript\n * const surface = useSurface()\n * const tween = createTween(\n * { dx: 0, dy: 0 },\n * {\n * to: { dx: 100, dy: 50 },\n * duration: 250,\n * ease: Easing.quadOut,\n * onUpdate: (t) => updateScroll({ dx: t.dx, dy: t.dy }),\n * onComplete: () => console.log('done'),\n * },\n * surface.scheduler,\n * )\n * // later: tween.stop()\n * ```\n */\nexport function createTween<T extends Record<string, number>>(\n initial: T,\n config: TweenConfig<T>,\n scheduler: HostScheduler\n): TweenHandle<T> {\n const target = { ...initial } as T\n const startValues = { ...initial } as T\n const keys = Object.keys(config.to) as (keyof T)[]\n const ease = config.ease ?? Easing.quadOut\n const startTime = performance.now()\n let frameHandle: HostCancelHandle | null = null\n let completed = false\n\n // Duration ≤ 0: snap immediately\n if (config.duration <= 0) {\n for (const key of keys) {\n const toVal = config.to[key]\n if (toVal !== undefined) {\n ;(target as Record<string, number>)[key as string] = toVal\n }\n }\n config.onUpdate?.(target)\n config.onComplete?.()\n return {\n target,\n stop: () => {},\n isActive: false,\n }\n }\n\n frameHandle = scheduler.onFrame((_dt: number) => {\n if (completed) return\n\n const elapsed = performance.now() - startTime\n const rawProgress = Math.min(elapsed / config.duration, 1)\n const easedProgress = ease(rawProgress)\n\n for (const key of keys) {\n const toVal = config.to[key]\n if (toVal !== undefined) {\n const fromVal: number = startValues[key] as number\n ;(target as Record<string, number>)[key as string] =\n fromVal + (toVal - fromVal) * easedProgress\n }\n }\n\n try {\n config.onUpdate?.(target)\n } catch {\n // Swallow onUpdate errors — don't break the animation loop\n }\n\n if (rawProgress >= 1) {\n completed = true\n frameHandle?.cancel()\n try {\n config.onComplete?.()\n } catch {\n // Swallow onComplete errors\n }\n }\n })\n\n return {\n target,\n stop: () => {\n if (completed) return\n completed = true\n frameHandle?.cancel()\n },\n get isActive(): boolean {\n return !completed\n },\n }\n}\n"],"names":["Easing"],"mappings":";;;AAqFO,SAAS,YACd,SACA,QACA,WACgB;AAChB,QAAM,SAAS,EAAE,GAAG,QAAA;AACpB,QAAM,cAAc,EAAE,GAAG,QAAA;AACzB,QAAM,OAAO,OAAO,KAAK,OAAO,EAAE;AAClC,QAAM,OAAO,OAAO,QAAQA,OAAAA,OAAO;AACnC,QAAM,YAAY,YAAY,IAAA;AAC9B,MAAI,cAAuC;AAC3C,MAAI,YAAY;AAGhB,MAAI,OAAO,YAAY,GAAG;AACxB,eAAW,OAAO,MAAM;AACtB,YAAM,QAAQ,OAAO,GAAG,GAAG;AAC3B,UAAI,UAAU,QAAW;AACrB,eAAkC,GAAa,IAAI;AAAA,MACvD;AAAA,IACF;AACA,WAAO,WAAW,MAAM;AACxB,WAAO,aAAA;AACP,WAAO;AAAA,MACL;AAAA,MACA,MAAM,MAAM;AAAA,MAAC;AAAA,MACb,UAAU;AAAA,IAAA;AAAA,EAEd;AAEA,gBAAc,UAAU,QAAQ,CAAC,QAAgB;AAC/C,QAAI,UAAW;AAEf,UAAM,UAAU,YAAY,IAAA,IAAQ;AACpC,UAAM,cAAc,KAAK,IAAI,UAAU,OAAO,UAAU,CAAC;AACzD,UAAM,gBAAgB,KAAK,WAAW;AAEtC,eAAW,OAAO,MAAM;AACtB,YAAM,QAAQ,OAAO,GAAG,GAAG;AAC3B,UAAI,UAAU,QAAW;AACvB,cAAM,UAAkB,YAAY,GAAG;AACrC,eAAkC,GAAa,IAC/C,WAAW,QAAQ,WAAW;AAAA,MAClC;AAAA,IACF;AAEA,QAAI;AACF,aAAO,WAAW,MAAM;AAAA,IAC1B,QAAQ;AAAA,IAER;AAEA,QAAI,eAAe,GAAG;AACpB,kBAAY;AACZ,mBAAa,OAAA;AACb,UAAI;AACF,eAAO,aAAA;AAAA,MACT,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA,MAAM,MAAM;AACV,UAAI,UAAW;AACf,kBAAY;AACZ,mBAAa,OAAA;AAAA,IACf;AAAA,IACA,IAAI,WAAoB;AACtB,aAAO,CAAC;AAAA,IACV;AAAA,EAAA;AAEJ;;;"}
@@ -0,0 +1,60 @@
1
+ import { HostScheduler } from '../host-adapter';
2
+ import { EasingFn } from './easing';
3
+ export { Easing, type EasingFn } from './easing';
4
+ /**
5
+ * Configuration for a tween animation.
6
+ * @typeParam T - Record of numeric properties to animate
7
+ */
8
+ export interface TweenConfig<T extends Record<string, number>> {
9
+ /** Target values (partial — only specified keys are animated) */
10
+ to: Partial<T>;
11
+ /** Duration in milliseconds */
12
+ duration: number;
13
+ /** Easing function (default: quadOut) */
14
+ ease?: EasingFn | undefined;
15
+ /** Called every frame with the current interpolated target */
16
+ onUpdate?: ((target: T) => void) | undefined;
17
+ /** Called when the tween completes naturally (not on stop()) */
18
+ onComplete?: (() => void) | undefined;
19
+ }
20
+ /**
21
+ * Handle to an active tween animation.
22
+ */
23
+ export interface TweenHandle<T extends Record<string, number> = Record<string, number>> {
24
+ /** Current interpolated state (mutated each frame) */
25
+ readonly target: Readonly<T>;
26
+ /** Stop the animation immediately. onComplete is NOT called. */
27
+ stop(): void;
28
+ /** Whether the animation is still running */
29
+ readonly isActive: boolean;
30
+ }
31
+ /**
32
+ * Start a time-based value interpolation.
33
+ *
34
+ * Animates numeric properties of a plain object from their current values
35
+ * to the specified target values over a fixed duration.
36
+ *
37
+ * @param initial - Starting values for all animated properties
38
+ * @param config - Animation configuration (target, duration, easing, callbacks)
39
+ * @param scheduler - HostScheduler for frame updates (from SurfaceContext)
40
+ * @returns TweenHandle for tracking and cancellation
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * const surface = useSurface()
45
+ * const tween = createTween(
46
+ * { dx: 0, dy: 0 },
47
+ * {
48
+ * to: { dx: 100, dy: 50 },
49
+ * duration: 250,
50
+ * ease: Easing.quadOut,
51
+ * onUpdate: (t) => updateScroll({ dx: t.dx, dy: t.dy }),
52
+ * onComplete: () => console.log('done'),
53
+ * },
54
+ * surface.scheduler,
55
+ * )
56
+ * // later: tween.stop()
57
+ * ```
58
+ */
59
+ export declare function createTween<T extends Record<string, number>>(initial: T, config: TweenConfig<T>, scheduler: HostScheduler): TweenHandle<T>;
60
+ //# sourceMappingURL=tween.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tween.d.ts","sourceRoot":"","sources":["../../src/animation/tween.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAoB,aAAa,EAAE,MAAM,iBAAiB,CAAA;AACtE,OAAO,EAAU,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAA;AAGhD,OAAO,EAAE,MAAM,EAAE,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAA;AAMhD;;;GAGG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAC3D,iEAAiE;IACjE,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IACd,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAA;IAChB,yCAAyC;IACzC,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;IAC3B,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IAC5C,gEAAgE;IAChE,UAAU,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAA;CACtC;AAED;;GAEG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IACpF,sDAAsD;IACtD,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAA;IAC5B,gEAAgE;IAChE,IAAI,IAAI,IAAI,CAAA;IACZ,6CAA6C;IAC7C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;CAC3B;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC1D,OAAO,EAAE,CAAC,EACV,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,EACtB,SAAS,EAAE,aAAa,GACvB,WAAW,CAAC,CAAC,CAAC,CAsEhB"}
@@ -0,0 +1,67 @@
1
+ import { Easing } from "./easing.js";
2
+ function createTween(initial, config, scheduler) {
3
+ const target = { ...initial };
4
+ const startValues = { ...initial };
5
+ const keys = Object.keys(config.to);
6
+ const ease = config.ease ?? Easing.quadOut;
7
+ const startTime = performance.now();
8
+ let frameHandle = null;
9
+ let completed = false;
10
+ if (config.duration <= 0) {
11
+ for (const key of keys) {
12
+ const toVal = config.to[key];
13
+ if (toVal !== void 0) {
14
+ target[key] = toVal;
15
+ }
16
+ }
17
+ config.onUpdate?.(target);
18
+ config.onComplete?.();
19
+ return {
20
+ target,
21
+ stop: () => {
22
+ },
23
+ isActive: false
24
+ };
25
+ }
26
+ frameHandle = scheduler.onFrame((_dt) => {
27
+ if (completed) return;
28
+ const elapsed = performance.now() - startTime;
29
+ const rawProgress = Math.min(elapsed / config.duration, 1);
30
+ const easedProgress = ease(rawProgress);
31
+ for (const key of keys) {
32
+ const toVal = config.to[key];
33
+ if (toVal !== void 0) {
34
+ const fromVal = startValues[key];
35
+ target[key] = fromVal + (toVal - fromVal) * easedProgress;
36
+ }
37
+ }
38
+ try {
39
+ config.onUpdate?.(target);
40
+ } catch {
41
+ }
42
+ if (rawProgress >= 1) {
43
+ completed = true;
44
+ frameHandle?.cancel();
45
+ try {
46
+ config.onComplete?.();
47
+ } catch {
48
+ }
49
+ }
50
+ });
51
+ return {
52
+ target,
53
+ stop: () => {
54
+ if (completed) return;
55
+ completed = true;
56
+ frameHandle?.cancel();
57
+ },
58
+ get isActive() {
59
+ return !completed;
60
+ }
61
+ };
62
+ }
63
+ export {
64
+ Easing,
65
+ createTween
66
+ };
67
+ //# sourceMappingURL=tween.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tween.js","sources":["../../src/animation/tween.ts"],"sourcesContent":["/**\n * Animation utilities — renderer-neutral tween engine.\n *\n * Provides time-based value interpolation with configurable easing.\n * Runs on HostScheduler.onFrame() — no renderer-specific tween system needed.\n *\n * This is the duration+easing counterpart to the spring-physics animation\n * (useSpring). Use createTween when you need exact duration control;\n * use useSpring for continuous physics-based motion.\n *\n * @migration(audit:surface-access) Phase 7 — new Core capability.\n * Replaces scene.tweens.add() in portable components.\n */\n\nimport type { HostCancelHandle, HostScheduler } from '../host-adapter'\nimport { Easing, type EasingFn } from './easing'\n\n// Re-export for backward compatibility — all easing functions now live in easing.ts\nexport { Easing, type EasingFn } from './easing'\n\n// ---------------------------------------------------------------------------\n// Tween types\n// ---------------------------------------------------------------------------\n\n/**\n * Configuration for a tween animation.\n * @typeParam T - Record of numeric properties to animate\n */\nexport interface TweenConfig<T extends Record<string, number>> {\n /** Target values (partial — only specified keys are animated) */\n to: Partial<T>\n /** Duration in milliseconds */\n duration: number\n /** Easing function (default: quadOut) */\n ease?: EasingFn | undefined\n /** Called every frame with the current interpolated target */\n onUpdate?: ((target: T) => void) | undefined\n /** Called when the tween completes naturally (not on stop()) */\n onComplete?: (() => void) | undefined\n}\n\n/**\n * Handle to an active tween animation.\n */\nexport interface TweenHandle<T extends Record<string, number> = Record<string, number>> {\n /** Current interpolated state (mutated each frame) */\n readonly target: Readonly<T>\n /** Stop the animation immediately. onComplete is NOT called. */\n stop(): void\n /** Whether the animation is still running */\n readonly isActive: boolean\n}\n\n// ---------------------------------------------------------------------------\n// createTween\n// ---------------------------------------------------------------------------\n\n/**\n * Start a time-based value interpolation.\n *\n * Animates numeric properties of a plain object from their current values\n * to the specified target values over a fixed duration.\n *\n * @param initial - Starting values for all animated properties\n * @param config - Animation configuration (target, duration, easing, callbacks)\n * @param scheduler - HostScheduler for frame updates (from SurfaceContext)\n * @returns TweenHandle for tracking and cancellation\n *\n * @example\n * ```typescript\n * const surface = useSurface()\n * const tween = createTween(\n * { dx: 0, dy: 0 },\n * {\n * to: { dx: 100, dy: 50 },\n * duration: 250,\n * ease: Easing.quadOut,\n * onUpdate: (t) => updateScroll({ dx: t.dx, dy: t.dy }),\n * onComplete: () => console.log('done'),\n * },\n * surface.scheduler,\n * )\n * // later: tween.stop()\n * ```\n */\nexport function createTween<T extends Record<string, number>>(\n initial: T,\n config: TweenConfig<T>,\n scheduler: HostScheduler\n): TweenHandle<T> {\n const target = { ...initial } as T\n const startValues = { ...initial } as T\n const keys = Object.keys(config.to) as (keyof T)[]\n const ease = config.ease ?? Easing.quadOut\n const startTime = performance.now()\n let frameHandle: HostCancelHandle | null = null\n let completed = false\n\n // Duration ≤ 0: snap immediately\n if (config.duration <= 0) {\n for (const key of keys) {\n const toVal = config.to[key]\n if (toVal !== undefined) {\n ;(target as Record<string, number>)[key as string] = toVal\n }\n }\n config.onUpdate?.(target)\n config.onComplete?.()\n return {\n target,\n stop: () => {},\n isActive: false,\n }\n }\n\n frameHandle = scheduler.onFrame((_dt: number) => {\n if (completed) return\n\n const elapsed = performance.now() - startTime\n const rawProgress = Math.min(elapsed / config.duration, 1)\n const easedProgress = ease(rawProgress)\n\n for (const key of keys) {\n const toVal = config.to[key]\n if (toVal !== undefined) {\n const fromVal: number = startValues[key] as number\n ;(target as Record<string, number>)[key as string] =\n fromVal + (toVal - fromVal) * easedProgress\n }\n }\n\n try {\n config.onUpdate?.(target)\n } catch {\n // Swallow onUpdate errors — don't break the animation loop\n }\n\n if (rawProgress >= 1) {\n completed = true\n frameHandle?.cancel()\n try {\n config.onComplete?.()\n } catch {\n // Swallow onComplete errors\n }\n }\n })\n\n return {\n target,\n stop: () => {\n if (completed) return\n completed = true\n frameHandle?.cancel()\n },\n get isActive(): boolean {\n return !completed\n },\n }\n}\n"],"names":[],"mappings":";AAqFO,SAAS,YACd,SACA,QACA,WACgB;AAChB,QAAM,SAAS,EAAE,GAAG,QAAA;AACpB,QAAM,cAAc,EAAE,GAAG,QAAA;AACzB,QAAM,OAAO,OAAO,KAAK,OAAO,EAAE;AAClC,QAAM,OAAO,OAAO,QAAQ,OAAO;AACnC,QAAM,YAAY,YAAY,IAAA;AAC9B,MAAI,cAAuC;AAC3C,MAAI,YAAY;AAGhB,MAAI,OAAO,YAAY,GAAG;AACxB,eAAW,OAAO,MAAM;AACtB,YAAM,QAAQ,OAAO,GAAG,GAAG;AAC3B,UAAI,UAAU,QAAW;AACrB,eAAkC,GAAa,IAAI;AAAA,MACvD;AAAA,IACF;AACA,WAAO,WAAW,MAAM;AACxB,WAAO,aAAA;AACP,WAAO;AAAA,MACL;AAAA,MACA,MAAM,MAAM;AAAA,MAAC;AAAA,MACb,UAAU;AAAA,IAAA;AAAA,EAEd;AAEA,gBAAc,UAAU,QAAQ,CAAC,QAAgB;AAC/C,QAAI,UAAW;AAEf,UAAM,UAAU,YAAY,IAAA,IAAQ;AACpC,UAAM,cAAc,KAAK,IAAI,UAAU,OAAO,UAAU,CAAC;AACzD,UAAM,gBAAgB,KAAK,WAAW;AAEtC,eAAW,OAAO,MAAM;AACtB,YAAM,QAAQ,OAAO,GAAG,GAAG;AAC3B,UAAI,UAAU,QAAW;AACvB,cAAM,UAAkB,YAAY,GAAG;AACrC,eAAkC,GAAa,IAC/C,WAAW,QAAQ,WAAW;AAAA,MAClC;AAAA,IACF;AAEA,QAAI;AACF,aAAO,WAAW,MAAM;AAAA,IAC1B,QAAQ;AAAA,IAER;AAEA,QAAI,eAAe,GAAG;AACpB,kBAAY;AACZ,mBAAa,OAAA;AACb,UAAI;AACF,eAAO,aAAA;AAAA,MACT,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA,MAAM,MAAM;AACV,UAAI,UAAW;AACf,kBAAY;AACZ,mBAAa,OAAA;AAAA,IACf;AAAA,IACA,IAAI,WAAoB;AACtB,aAAO,CAAC;AAAA,IACV;AAAA,EAAA;AAEJ;"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Core animation value types — shared by tween, spring, and effect engine.
3
+ *
4
+ * These types define the common property set that all renderer nodes
5
+ * (Pixi, Phaser) can animate. Renderer-specific mappings translate
6
+ * AnimValues to native properties.
7
+ */
8
+ /** Animatable numeric properties on a renderer node */
9
+ export interface AnimValues {
10
+ x: number;
11
+ y: number;
12
+ scaleX: number;
13
+ scaleY: number;
14
+ rotation: number;
15
+ alpha: number;
16
+ [key: string]: number;
17
+ }
18
+ /** Configuration passed to applyEffect / effect definitions */
19
+ export interface EffectConfig {
20
+ /** Intensity multiplier (0..2, default varies per effect) */
21
+ intensity?: number | undefined;
22
+ /** Duration in ms (default varies per effect) */
23
+ time?: number | undefined;
24
+ /** Shake/wobble magnitude */
25
+ magnitude?: number | undefined;
26
+ /** Slide direction */
27
+ direction?: 'left' | 'right' | 'up' | 'down' | undefined;
28
+ /** Callback when effect completes */
29
+ onComplete?: (() => void) | undefined;
30
+ }
31
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/animation/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,uDAAuD;AACvD,MAAM,WAAW,UAAU;IACzB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CACtB;AAED,+DAA+D;AAC/D,MAAM,WAAW,YAAY;IAC3B,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC9B,iDAAiD;IACjD,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC9B,sBAAsB;IACtB,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAAA;IACxD,qCAAqC;IACrC,UAAU,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAA;CACtC"}
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const signalsCore = require("@preact/signals-core");
4
+ const hooks = require("../hooks.cjs");
5
+ const animatedSignal = require("./animated-signal.cjs");
6
+ const springPhysics = require("./spring-physics.cjs");
7
+ function useSpring(initialValue, config, onComplete) {
8
+ const resolvedConfig = typeof config === "string" ? springPhysics.SPRING_PRESETS[config] : config ?? springPhysics.SPRING_PRESETS.default;
9
+ const valueSignal = hooks.useRef(null);
10
+ if (valueSignal.current === null) {
11
+ valueSignal.current = animatedSignal.animatedSignal(initialValue);
12
+ }
13
+ const ctx = hooks.getCurrentCtx();
14
+ const scheduler = ctx?.surface?.scheduler ?? null;
15
+ const state = hooks.useRef({ value: initialValue, velocity: 0 });
16
+ const target = hooks.useRef({ value: initialValue });
17
+ const physics = hooks.useRef(new springPhysics.SpringPhysics(resolvedConfig));
18
+ const schedulerRef = hooks.useRef(null);
19
+ const wasAtRest = hooks.useRef(true);
20
+ if (scheduler && schedulerRef.current !== scheduler) {
21
+ schedulerRef.current = scheduler;
22
+ }
23
+ const setValueRef = hooks.useRef(
24
+ null
25
+ );
26
+ if (!setValueRef.current) {
27
+ setValueRef.current = (newTarget) => {
28
+ const nextTarget = typeof newTarget === "function" ? newTarget(target.current.value) : newTarget;
29
+ target.current.value = nextTarget;
30
+ };
31
+ }
32
+ hooks.useEffect(() => {
33
+ const currentScheduler = schedulerRef.current;
34
+ if (!currentScheduler) {
35
+ console.warn("useSpring: HostScheduler not found in surface context");
36
+ return;
37
+ }
38
+ const updateListener = (delta) => {
39
+ const currentTarget = target.current.value;
40
+ const currentState = state.current;
41
+ const atRest = physics.current.isAtRest(currentState, currentTarget);
42
+ if (wasAtRest.current && !atRest) {
43
+ resolvedConfig.onStart?.();
44
+ }
45
+ if (atRest && !wasAtRest.current && onComplete) {
46
+ onComplete();
47
+ }
48
+ wasAtRest.current = atRest;
49
+ if (atRest) {
50
+ const snapped = physics.current.snapIfAtRest(currentState, currentTarget);
51
+ if (snapped.value !== currentState.value && valueSignal.current) {
52
+ state.current = snapped;
53
+ valueSignal.current.value = snapped.value;
54
+ }
55
+ return;
56
+ }
57
+ const deltaSeconds = delta / 1e3;
58
+ const newState = physics.current.step(currentState, currentTarget, deltaSeconds);
59
+ resolvedConfig.onUpdate?.(newState.value);
60
+ if (valueSignal.current) {
61
+ state.current = newState;
62
+ valueSignal.current.value = newState.value;
63
+ }
64
+ };
65
+ const handle = currentScheduler.onFrame(updateListener);
66
+ return () => {
67
+ handle.cancel();
68
+ };
69
+ }, []);
70
+ if (!valueSignal.current) {
71
+ throw new Error("useSpring: Failed to initialize animated signal");
72
+ }
73
+ return [
74
+ valueSignal.current,
75
+ setValueRef.current
76
+ ];
77
+ }
78
+ function useSprings(initialValues, config) {
79
+ const keys = Object.keys(initialValues);
80
+ const springsMap = hooks.useRef(/* @__PURE__ */ new Map());
81
+ const schedulerRef = hooks.useRef(null);
82
+ const resolvedConfig = typeof config === "string" ? springPhysics.SPRING_PRESETS[config] : config ?? springPhysics.SPRING_PRESETS.default;
83
+ keys.forEach((key) => {
84
+ if (!springsMap.current.has(key)) {
85
+ const initialValue = initialValues[key];
86
+ const signal = animatedSignal.animatedSignal(initialValue);
87
+ const target = { value: initialValue };
88
+ const state = { value: initialValue, velocity: 0 };
89
+ const physics = new springPhysics.SpringPhysics(resolvedConfig);
90
+ springsMap.current.set(key, { signal, target, state, physics });
91
+ }
92
+ });
93
+ const ctx = hooks.getCurrentCtx();
94
+ const scheduler = ctx?.surface?.scheduler ?? null;
95
+ if (scheduler && schedulerRef.current !== scheduler) {
96
+ schedulerRef.current = scheduler;
97
+ }
98
+ hooks.useEffect(() => {
99
+ const currentScheduler = schedulerRef.current;
100
+ if (!currentScheduler) {
101
+ console.warn("useSprings: HostScheduler not found in surface context");
102
+ return;
103
+ }
104
+ const updateListener = (delta) => {
105
+ const deltaSeconds = delta / 1e3;
106
+ springsMap.current.forEach((spring) => {
107
+ const currentTarget = spring.target.value;
108
+ const currentState = spring.state;
109
+ if (spring.physics.isAtRest(currentState, currentTarget)) {
110
+ const snapped = spring.physics.snapIfAtRest(currentState, currentTarget);
111
+ if (snapped.value !== currentState.value) {
112
+ spring.state = snapped;
113
+ spring.signal.value = snapped.value;
114
+ }
115
+ return;
116
+ }
117
+ const newState = spring.physics.step(currentState, currentTarget, deltaSeconds);
118
+ spring.state = newState;
119
+ spring.signal.value = newState.value;
120
+ });
121
+ };
122
+ const handle = currentScheduler.onFrame(updateListener);
123
+ return () => {
124
+ handle.cancel();
125
+ };
126
+ }, []);
127
+ const signals = {};
128
+ keys.forEach((key) => {
129
+ const spring = springsMap.current.get(key);
130
+ if (spring) {
131
+ signals[key] = spring.signal;
132
+ }
133
+ });
134
+ const setValues = (values) => {
135
+ Object.entries(values).forEach(([key, value]) => {
136
+ const spring = springsMap.current.get(key);
137
+ if (spring) {
138
+ const nextTarget = typeof value === "function" ? value(spring.target.value) : value;
139
+ spring.target.value = nextTarget;
140
+ }
141
+ });
142
+ };
143
+ return [signals, setValues];
144
+ }
145
+ Object.defineProperty(exports, "computed", {
146
+ enumerable: true,
147
+ get: () => signalsCore.computed
148
+ });
149
+ exports.SPRING_PRESETS = springPhysics.SPRING_PRESETS;
150
+ exports.useSpring = useSpring;
151
+ exports.useSprings = useSprings;
152
+ //# sourceMappingURL=useSpring.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSpring.cjs","sources":["../../src/animation/useSpring.ts"],"sourcesContent":["/**\n * Spring animation hook\n * Provides physics-based animations using signals for direct updates.\n * Uses HostScheduler.onFrame() — no renderer-specific tick source needed.\n *\n * @migration(audit:surface-access) Phase 7 — moved to @number10/jsx-core ✅\n */\nimport { computed, type Signal } from '@preact/signals-core'\nimport { getCurrentCtx, useEffect, useRef, type HookContext } from '../hooks'\nimport type { HostScheduler } from '../host-adapter'\nimport { animatedSignal, type AnimatedSignal } from './animated-signal'\nimport {\n SPRING_PRESETS,\n SpringPhysics,\n type SpringConfig,\n type SpringState,\n} from './spring-physics'\n\n/**\n * Spring animation hook\n * Creates an animated signal that smoothly transitions to target values\n *\n * @param initialValue - Initial value\n * @param config - Spring configuration or preset name\n * @param onComplete - Optional callback when spring reaches rest\n * @returns Tuple of [animated signal, setter function]\n *\n * @example\n * ```tsx\n * // ✓ SAFE - leaf node\n * const [x, setX] = useSpring(0, 'wobbly')\n * <View x={x} width={50} height={50} backgroundColor={0xff0000} />\n *\n * // ✓ SAFE - transform properties\n * const [scale, setScale] = useSpring(1, 'default')\n * <View scale={scale}><Text text=\"Hello\" /></View>\n *\n * // ✗ UNSAFE - container with percentage children\n * const [width, setWidth] = useSpring(200, 'wobbly')\n * <View width={width}><NineSlice width=\"100%\" /></View> // BREAKS!\n * ```\n */\nexport function useSpring(\n initialValue: number,\n config?: SpringConfig | keyof typeof SPRING_PRESETS,\n onComplete?: () => void\n): [AnimatedSignal, (target: number | ((prev: number) => number)) => void] {\n // Resolve config\n const resolvedConfig =\n typeof config === 'string' ? SPRING_PRESETS[config] : (config ?? SPRING_PRESETS.default)\n\n // Create animated signal\n const valueSignal = useRef<AnimatedSignal | null>(null)\n if (valueSignal.current === null) {\n valueSignal.current = animatedSignal(initialValue)\n }\n\n // Capture scheduler from surface context during render\n const ctx = getCurrentCtx() as HookContext | null\n const scheduler = ctx?.surface?.scheduler ?? null\n\n // Physics state\n const state = useRef<SpringState>({ value: initialValue, velocity: 0 })\n const target = useRef<Signal<number>>({ value: initialValue } as Signal<number>)\n const physics = useRef(new SpringPhysics(resolvedConfig))\n const schedulerRef = useRef<HostScheduler | null>(null)\n const wasAtRest = useRef<boolean>(true)\n\n // Update scheduler ref if it changes\n if (scheduler && schedulerRef.current !== scheduler) {\n schedulerRef.current = scheduler\n }\n\n // Setter function — stable reference via ref (avoids effect re-runs)\n const setValueRef = useRef<((newTarget: number | ((prev: number) => number)) => void) | null>(\n null\n )\n if (!setValueRef.current) {\n setValueRef.current = (newTarget: number | ((prev: number) => number)) => {\n const nextTarget =\n typeof newTarget === 'function' ? newTarget(target.current.value) : newTarget\n target.current.value = nextTarget\n }\n }\n\n useEffect(() => {\n const currentScheduler = schedulerRef.current\n if (!currentScheduler) {\n console.warn('useSpring: HostScheduler not found in surface context')\n return\n }\n\n // Physics update loop\n const updateListener = (delta: number) => {\n const currentTarget = target.current.value\n const currentState = state.current\n\n // Check if at rest\n const atRest = physics.current.isAtRest(currentState, currentTarget)\n\n // Fire onStart when transitioning from rest to active\n if (wasAtRest.current && !atRest) {\n ;(resolvedConfig as SpringConfig).onStart?.()\n }\n\n // Call onComplete if just reached rest\n if (atRest && !wasAtRest.current && onComplete) {\n onComplete()\n }\n wasAtRest.current = atRest\n\n // Skip if already at rest\n if (atRest) {\n // Snap to target\n const snapped = physics.current.snapIfAtRest(currentState, currentTarget)\n if (snapped.value !== currentState.value && valueSignal.current) {\n state.current = snapped\n valueSignal.current.value = snapped.value\n }\n return\n }\n\n // Perform physics step\n const deltaSeconds = delta / 1000\n const newState = physics.current.step(currentState, currentTarget, deltaSeconds)\n\n // Fire onUpdate with current value every frame while active\n ;(resolvedConfig as SpringConfig).onUpdate?.(newState.value)\n\n // Update state and signal\n if (valueSignal.current) {\n state.current = newState\n valueSignal.current.value = newState.value\n }\n }\n\n const handle = currentScheduler.onFrame(updateListener)\n return () => {\n handle.cancel()\n }\n }, [])\n\n if (!valueSignal.current) {\n throw new Error('useSpring: Failed to initialize animated signal')\n }\n\n return [\n valueSignal.current,\n setValueRef.current as (target: number | ((prev: number) => number)) => void,\n ]\n}\n\n/**\n * Multi-spring animation hook\n * Creates multiple animated signals that share the same configuration\n * @param initialValues - Object with initial values\n * @param config - Spring configuration or preset name\n * @returns Tuple of [animated signals object, setter function]\n *\n * @example\n * ```tsx\n * const [pos, setPos] = useSprings({ x: 0, y: 0 }, 'wobbly')\n * <View x={pos.x} y={pos.y} />\n * setPos({ x: 100, y: 200 })\n * ```\n */\nexport function useSprings<T extends Record<string, number>>(\n initialValues: T,\n config?: SpringConfig | keyof typeof SPRING_PRESETS\n): [\n { [K in keyof T]: AnimatedSignal },\n (values: Partial<{ [K in keyof T]: number | ((prev: number) => number) }>) => void,\n] {\n const keys = Object.keys(initialValues) as (keyof T)[]\n const springsMap = useRef<\n Map<\n keyof T,\n {\n signal: AnimatedSignal\n target: { value: number }\n state: SpringState\n physics: SpringPhysics\n }\n >\n >(new Map())\n const schedulerRef = useRef<HostScheduler | null>(null)\n\n // Resolve config\n const resolvedConfig =\n typeof config === 'string' ? SPRING_PRESETS[config] : (config ?? SPRING_PRESETS.default)\n\n // Initialize springs manually (can't use hooks in loops!)\n keys.forEach((key) => {\n if (!springsMap.current.has(key)) {\n const initialValue = initialValues[key] as number\n const signal = animatedSignal(initialValue)\n const target = { value: initialValue }\n const state = { value: initialValue, velocity: 0 }\n const physics = new SpringPhysics(resolvedConfig)\n springsMap.current.set(key, { signal, target, state, physics })\n }\n })\n\n // Capture scheduler from surface context during render\n const ctx = getCurrentCtx() as HookContext | null\n const scheduler = ctx?.surface?.scheduler ?? null\n\n if (scheduler && schedulerRef.current !== scheduler) {\n schedulerRef.current = scheduler\n }\n\n // Setup physics update loop\n useEffect(() => {\n const currentScheduler = schedulerRef.current\n if (!currentScheduler) {\n console.warn('useSprings: HostScheduler not found in surface context')\n return\n }\n\n const updateListener = (delta: number) => {\n const deltaSeconds = delta / 1000\n\n springsMap.current.forEach((spring) => {\n const currentTarget = spring.target.value\n const currentState = spring.state\n\n // Skip if already at rest\n if (spring.physics.isAtRest(currentState, currentTarget)) {\n const snapped = spring.physics.snapIfAtRest(currentState, currentTarget)\n if (snapped.value !== currentState.value) {\n spring.state = snapped\n spring.signal.value = snapped.value\n }\n return\n }\n\n // Perform physics step\n const newState = spring.physics.step(currentState, currentTarget, deltaSeconds)\n spring.state = newState\n spring.signal.value = newState.value\n })\n }\n\n const handle = currentScheduler.onFrame(updateListener)\n return () => {\n handle.cancel()\n }\n }, [])\n\n // Build result object\n const signals = {} as { [K in keyof T]: AnimatedSignal }\n keys.forEach((key) => {\n const spring = springsMap.current.get(key)\n if (spring) {\n signals[key] = spring.signal\n }\n })\n\n // Setter function\n const setValues = (values: Partial<{ [K in keyof T]: number | ((prev: number) => number) }>) => {\n Object.entries(values).forEach(([key, value]) => {\n const spring = springsMap.current.get(key as keyof T)\n if (spring) {\n const nextTarget = typeof value === 'function' ? value(spring.target.value) : value\n spring.target.value = nextTarget as number\n }\n })\n }\n\n return [signals, setValues]\n}\n\n/**\n * Export presets for convenience\n */\nexport { computed, SPRING_PRESETS }\n"],"names":["SPRING_PRESETS","useRef","animatedSignal","getCurrentCtx","SpringPhysics","useEffect"],"mappings":";;;;;;AA0CO,SAAS,UACd,cACA,QACA,YACyE;AAEzE,QAAM,iBACJ,OAAO,WAAW,WAAWA,cAAAA,eAAe,MAAM,IAAK,UAAUA,cAAAA,eAAe;AAGlF,QAAM,cAAcC,MAAAA,OAA8B,IAAI;AACtD,MAAI,YAAY,YAAY,MAAM;AAChC,gBAAY,UAAUC,eAAAA,eAAe,YAAY;AAAA,EACnD;AAGA,QAAM,MAAMC,MAAAA,cAAA;AACZ,QAAM,YAAY,KAAK,SAAS,aAAa;AAG7C,QAAM,QAAQF,MAAAA,OAAoB,EAAE,OAAO,cAAc,UAAU,GAAG;AACtE,QAAM,SAASA,MAAAA,OAAuB,EAAE,OAAO,cAAgC;AAC/E,QAAM,UAAUA,MAAAA,OAAO,IAAIG,cAAAA,cAAc,cAAc,CAAC;AACxD,QAAM,eAAeH,MAAAA,OAA6B,IAAI;AACtD,QAAM,YAAYA,MAAAA,OAAgB,IAAI;AAGtC,MAAI,aAAa,aAAa,YAAY,WAAW;AACnD,iBAAa,UAAU;AAAA,EACzB;AAGA,QAAM,cAAcA,MAAAA;AAAAA,IAClB;AAAA,EAAA;AAEF,MAAI,CAAC,YAAY,SAAS;AACxB,gBAAY,UAAU,CAAC,cAAmD;AACxE,YAAM,aACJ,OAAO,cAAc,aAAa,UAAU,OAAO,QAAQ,KAAK,IAAI;AACtE,aAAO,QAAQ,QAAQ;AAAA,IACzB;AAAA,EACF;AAEAI,QAAAA,UAAU,MAAM;AACd,UAAM,mBAAmB,aAAa;AACtC,QAAI,CAAC,kBAAkB;AACrB,cAAQ,KAAK,uDAAuD;AACpE;AAAA,IACF;AAGA,UAAM,iBAAiB,CAAC,UAAkB;AACxC,YAAM,gBAAgB,OAAO,QAAQ;AACrC,YAAM,eAAe,MAAM;AAG3B,YAAM,SAAS,QAAQ,QAAQ,SAAS,cAAc,aAAa;AAGnE,UAAI,UAAU,WAAW,CAAC,QAAQ;AAC9B,uBAAgC,UAAA;AAAA,MACpC;AAGA,UAAI,UAAU,CAAC,UAAU,WAAW,YAAY;AAC9C,mBAAA;AAAA,MACF;AACA,gBAAU,UAAU;AAGpB,UAAI,QAAQ;AAEV,cAAM,UAAU,QAAQ,QAAQ,aAAa,cAAc,aAAa;AACxE,YAAI,QAAQ,UAAU,aAAa,SAAS,YAAY,SAAS;AAC/D,gBAAM,UAAU;AAChB,sBAAY,QAAQ,QAAQ,QAAQ;AAAA,QACtC;AACA;AAAA,MACF;AAGA,YAAM,eAAe,QAAQ;AAC7B,YAAM,WAAW,QAAQ,QAAQ,KAAK,cAAc,eAAe,YAAY;AAG7E,qBAAgC,WAAW,SAAS,KAAK;AAG3D,UAAI,YAAY,SAAS;AACvB,cAAM,UAAU;AAChB,oBAAY,QAAQ,QAAQ,SAAS;AAAA,MACvC;AAAA,IACF;AAEA,UAAM,SAAS,iBAAiB,QAAQ,cAAc;AACtD,WAAO,MAAM;AACX,aAAO,OAAA;AAAA,IACT;AAAA,EACF,GAAG,CAAA,CAAE;AAEL,MAAI,CAAC,YAAY,SAAS;AACxB,UAAM,IAAI,MAAM,iDAAiD;AAAA,EACnE;AAEA,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,YAAY;AAAA,EAAA;AAEhB;AAgBO,SAAS,WACd,eACA,QAIA;AACA,QAAM,OAAO,OAAO,KAAK,aAAa;AACtC,QAAM,aAAaJ,MAAAA,OAUjB,oBAAI,KAAK;AACX,QAAM,eAAeA,MAAAA,OAA6B,IAAI;AAGtD,QAAM,iBACJ,OAAO,WAAW,WAAWD,cAAAA,eAAe,MAAM,IAAK,UAAUA,cAAAA,eAAe;AAGlF,OAAK,QAAQ,CAAC,QAAQ;AACpB,QAAI,CAAC,WAAW,QAAQ,IAAI,GAAG,GAAG;AAChC,YAAM,eAAe,cAAc,GAAG;AACtC,YAAM,SAASE,eAAAA,eAAe,YAAY;AAC1C,YAAM,SAAS,EAAE,OAAO,aAAA;AACxB,YAAM,QAAQ,EAAE,OAAO,cAAc,UAAU,EAAA;AAC/C,YAAM,UAAU,IAAIE,cAAAA,cAAc,cAAc;AAChD,iBAAW,QAAQ,IAAI,KAAK,EAAE,QAAQ,QAAQ,OAAO,SAAS;AAAA,IAChE;AAAA,EACF,CAAC;AAGD,QAAM,MAAMD,MAAAA,cAAA;AACZ,QAAM,YAAY,KAAK,SAAS,aAAa;AAE7C,MAAI,aAAa,aAAa,YAAY,WAAW;AACnD,iBAAa,UAAU;AAAA,EACzB;AAGAE,QAAAA,UAAU,MAAM;AACd,UAAM,mBAAmB,aAAa;AACtC,QAAI,CAAC,kBAAkB;AACrB,cAAQ,KAAK,wDAAwD;AACrE;AAAA,IACF;AAEA,UAAM,iBAAiB,CAAC,UAAkB;AACxC,YAAM,eAAe,QAAQ;AAE7B,iBAAW,QAAQ,QAAQ,CAAC,WAAW;AACrC,cAAM,gBAAgB,OAAO,OAAO;AACpC,cAAM,eAAe,OAAO;AAG5B,YAAI,OAAO,QAAQ,SAAS,cAAc,aAAa,GAAG;AACxD,gBAAM,UAAU,OAAO,QAAQ,aAAa,cAAc,aAAa;AACvE,cAAI,QAAQ,UAAU,aAAa,OAAO;AACxC,mBAAO,QAAQ;AACf,mBAAO,OAAO,QAAQ,QAAQ;AAAA,UAChC;AACA;AAAA,QACF;AAGA,cAAM,WAAW,OAAO,QAAQ,KAAK,cAAc,eAAe,YAAY;AAC9E,eAAO,QAAQ;AACf,eAAO,OAAO,QAAQ,SAAS;AAAA,MACjC,CAAC;AAAA,IACH;AAEA,UAAM,SAAS,iBAAiB,QAAQ,cAAc;AACtD,WAAO,MAAM;AACX,aAAO,OAAA;AAAA,IACT;AAAA,EACF,GAAG,CAAA,CAAE;AAGL,QAAM,UAAU,CAAA;AAChB,OAAK,QAAQ,CAAC,QAAQ;AACpB,UAAM,SAAS,WAAW,QAAQ,IAAI,GAAG;AACzC,QAAI,QAAQ;AACV,cAAQ,GAAG,IAAI,OAAO;AAAA,IACxB;AAAA,EACF,CAAC;AAGD,QAAM,YAAY,CAAC,WAA6E;AAC9F,WAAO,QAAQ,MAAM,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AAC/C,YAAM,SAAS,WAAW,QAAQ,IAAI,GAAc;AACpD,UAAI,QAAQ;AACV,cAAM,aAAa,OAAO,UAAU,aAAa,MAAM,OAAO,OAAO,KAAK,IAAI;AAC9E,eAAO,OAAO,QAAQ;AAAA,MACxB;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO,CAAC,SAAS,SAAS;AAC5B;;;;;;;;"}
@@ -0,0 +1,55 @@
1
+ import { computed } from '@preact/signals-core';
2
+ import { AnimatedSignal } from './animated-signal';
3
+ import { SPRING_PRESETS, SpringConfig } from './spring-physics';
4
+ /**
5
+ * Spring animation hook
6
+ * Creates an animated signal that smoothly transitions to target values
7
+ *
8
+ * @param initialValue - Initial value
9
+ * @param config - Spring configuration or preset name
10
+ * @param onComplete - Optional callback when spring reaches rest
11
+ * @returns Tuple of [animated signal, setter function]
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * // ✓ SAFE - leaf node
16
+ * const [x, setX] = useSpring(0, 'wobbly')
17
+ * <View x={x} width={50} height={50} backgroundColor={0xff0000} />
18
+ *
19
+ * // ✓ SAFE - transform properties
20
+ * const [scale, setScale] = useSpring(1, 'default')
21
+ * <View scale={scale}><Text text="Hello" /></View>
22
+ *
23
+ * // ✗ UNSAFE - container with percentage children
24
+ * const [width, setWidth] = useSpring(200, 'wobbly')
25
+ * <View width={width}><NineSlice width="100%" /></View> // BREAKS!
26
+ * ```
27
+ */
28
+ export declare function useSpring(initialValue: number, config?: SpringConfig | keyof typeof SPRING_PRESETS, onComplete?: () => void): [AnimatedSignal, (target: number | ((prev: number) => number)) => void];
29
+ /**
30
+ * Multi-spring animation hook
31
+ * Creates multiple animated signals that share the same configuration
32
+ * @param initialValues - Object with initial values
33
+ * @param config - Spring configuration or preset name
34
+ * @returns Tuple of [animated signals object, setter function]
35
+ *
36
+ * @example
37
+ * ```tsx
38
+ * const [pos, setPos] = useSprings({ x: 0, y: 0 }, 'wobbly')
39
+ * <View x={pos.x} y={pos.y} />
40
+ * setPos({ x: 100, y: 200 })
41
+ * ```
42
+ */
43
+ export declare function useSprings<T extends Record<string, number>>(initialValues: T, config?: SpringConfig | keyof typeof SPRING_PRESETS): [
44
+ {
45
+ [K in keyof T]: AnimatedSignal;
46
+ },
47
+ (values: Partial<{
48
+ [K in keyof T]: number | ((prev: number) => number);
49
+ }>) => void
50
+ ];
51
+ /**
52
+ * Export presets for convenience
53
+ */
54
+ export { computed, SPRING_PRESETS };
55
+ //# sourceMappingURL=useSpring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSpring.d.ts","sourceRoot":"","sources":["../../src/animation/useSpring.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,QAAQ,EAAe,MAAM,sBAAsB,CAAA;AAG5D,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACvE,OAAO,EACL,cAAc,EAEd,KAAK,YAAY,EAElB,MAAM,kBAAkB,CAAA;AAEzB;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,SAAS,CACvB,YAAY,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,YAAY,GAAG,MAAM,OAAO,cAAc,EACnD,UAAU,CAAC,EAAE,MAAM,IAAI,GACtB,CAAC,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,KAAK,IAAI,CAAC,CAwGzE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACzD,aAAa,EAAE,CAAC,EAChB,MAAM,CAAC,EAAE,YAAY,GAAG,MAAM,OAAO,cAAc,GAClD;IACD;SAAG,CAAC,IAAI,MAAM,CAAC,GAAG,cAAc;KAAE;IAClC,CAAC,MAAM,EAAE,OAAO,CAAC;SAAG,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;KAAE,CAAC,KAAK,IAAI;CACnF,CAkGA;AAED;;GAEG;AACH,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAA"}